[
  {
    "path": ".builds/debian-arm64.yml",
    "content": "image: debian/unstable\narch: arm64\npackages:\n    - clang\n    - make\n    - gcc\ntasks:\n    - clang-build: |\n        cd kakoune\n        make CXX=clang++ -j$(nproc)\n    - clang-test: |\n        cd kakoune\n        LC_ALL=C.utf8 make test\n    - gcc-build: |\n        cd kakoune\n        make clean\n        make CXX=g++ -j$(nproc)\n    - gcc-test: |\n        cd kakoune\n        LC_ALL=C.utf8 make test\n"
  },
  {
    "path": ".builds/debian.yml",
    "content": "image: debian/stable\npackages:\n    - clang\n    - gcc\n    - make\ntasks:\n    - clang-build: |\n        cd kakoune\n        make CXX=clang++ -j$(nproc)\n    - clang-test: |\n        cd kakoune\n        LC_ALL=C.utf8 make test\n    - gcc-build: |\n        cd kakoune\n        make clean\n        make CXX=g++ -j$(nproc)\n    - gcc-test: |\n        cd kakoune\n        LC_ALL=C.utf8 make test\n"
  },
  {
    "path": ".builds/freebsd.yml",
    "content": "image: freebsd/latest\npackages:\n    - gcc\ntasks:\n    - build-clang: |\n        cd kakoune\n        make CXX=clang++ -j$(sysctl -n hw.ncpu)\n    - test-clang: |\n        cd kakoune\n        LC_ALL=en_US.UTF-8 make test\n    - build-gcc: |\n        cd kakoune\n        make clean\n        make CXX=g++ -j$(sysctl -n hw.ncpu)\n    - test-gcc: |\n        cd kakoune\n        LC_ALL=en_US.UTF-8 make test\n"
  },
  {
    "path": ".builds/readme.yml",
    "content": "image: archlinux\noauth: git.sr.ht/REPOSITORIES:RW git.sr.ht/PROFILE:RO\npackages:\n    - hut\n    - asciidoctor\ntasks:\n    - update-readme: |\n        cd kakoune\n        asciidoctor -e README.asciidoc\n        hut git update --readme README.html --repo https://git.sr.ht/~mawww/kakoune\n\n"
  },
  {
    "path": ".cirrus.yml",
    "content": "# TODO move to Github Actions after https://github.com/actions/runner/issues/385\nfreebsd_task:\n    freebsd_instance:\n        image_family: freebsd-14-3\n    matrix:\n        - name: freebsd_clang\n          env:\n              CXX: clang++\n        - name: freebsd_gcc\n          gcc_script: pkg install -y gcc\n          env:\n              CXX: g++\n    test_script: make CXX=$CXX -j$(sysctl -n hw.ncpu) test\n    env:\n        LC_ALL: en_US.UTF-8\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: Report an unexpected behaviour with the editor\nlabels: bug\ntitle: \"[BUG] \"\nbody:\n\n    - type: markdown\n      attributes:\n        value: |\n            Please make sure to [search issues](https://github.com/mawww/kakoune/issues?q=is%3Aissue) before creating a new one, to avoid duplicates and centralise conversations.\n\n            If you need to have a question about the editor itself (options, behaviour, code…) answered, make sure to check the [documentation](https://github.com/mawww/kakoune/tree/master/doc/pages) (also available via the `doc` command in the editor) or drop by [IRC](https://web.libera.chat/?channels=#kakoune) to get instant feedback.\n\n    - type: input\n      attributes:\n          label: \"Version of Kakoune\"\n          description: \"If unsure, use the output of command: `kak -version`\"\n          placeholder: \"v2020.12.31\"\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: \"Reproducer\"\n          description: \"What are the steps to follow to reproduce the issue?\"\n          placeholder: |\n              If any specific configuration or environment settings are required to reproduce the issue, also describe them here.\n      validations:\n        required: true\n\n    - type: textarea\n      attributes:\n          label: \"Outcome\"\n          description: \"What is the outcome of the reproducing steps above?\"\n      validations:\n        required: true\n\n    - type: textarea\n      attributes:\n          label: \"Expectations\"\n          description: \"What was the expected outcome of the reproducing steps above?\"\n      validations:\n        required: true\n\n    - type: textarea\n      attributes:\n          label: \"Additional information\"\n          description: \"Any other information that may be relevant to diagnosing the issue should be documented here\"\n          placeholder: |\n              Examples of relevant information:\n\n              * What OS and distribution are you using?\n              * Are you running an experimental branch (i.e. not `master`)?\n              * Is the issue reproducible in other terminals?\n      validations:\n        required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n    - name: IRC channel\n      url: https://web.libera.chat/?channels=#kakoune\n      about: Ask your questions to get real time support on the official channel\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/crash_report.yml",
    "content": "name: Crash report\ndescription: Report a crash of the editor while in-use\nlabels: bug, crash\ntitle: \"[BUG][CRASH] \"\nbody:\n\n    - type: markdown\n      attributes:\n        value: |\n            Please make sure to [search issues](https://github.com/mawww/kakoune/issues?q=is%3Aissue) before creating a new one, to avoid duplicates and centralise conversations.\n\n            If you need to have a question about the editor itself (options, behaviour, code…) answered, make sure to check the [documentation](https://github.com/mawww/kakoune/tree/master/doc/pages) (also available via the `doc` command in the editor) or drop by [IRC](https://web.libera.chat/?channels=#kakoune) to get instant feedback.\n\n    - type: input\n      attributes:\n          label: \"Version of Kakoune\"\n          description: \"If unsure, use the output of command: `kak -version`\"\n          placeholder: \"v2020.12.31\"\n      validations:\n          required: true\n\n    - type: textarea\n      attributes:\n          label: \"Reproducer\"\n          description: \"What are the steps to follow to reproduce the issue?\"\n          placeholder: |\n              If any specific configuration or environment settings are required to reproduce the issue, also describe them here.\n      validations:\n        required: true\n\n    - type: textarea\n      attributes:\n          label: \"Outcome\"\n          description: \"What is the outcome of the reproducing steps above?\"\n          placeholder: |\n              If the editor printed any error messages, or if you managed to obtain a backtrace, also document them here. Make sure to demangle the stacktrace by passing it to the `c++filt` command.\n\n              Note that triggering the issue in the editor compiled in debug mode will produce more helpful messages, please consider re-building with `make debug=yes` to help us out.\n      validations:\n        required: true\n\n    - type: textarea\n      attributes:\n          label: \"Expectations\"\n          description: \"What was the expected outcome of the reproducing steps above?\"\n          placeholder: |\n              If the reproducing steps are self-evident, the expectations are simply “the editor should not crash”. In that case, you may ignore this field.\n      validations:\n        required: false\n\n    - type: textarea\n      attributes:\n          label: \"Additional information\"\n          description: \"Any other information that may be relevant to diagnosing the issue should be documented here\"\n          placeholder: |\n              Examples of relevant information:\n\n              * What OS and distribution are you using?\n              * Are you running an experimental branch (i.e. not `master`)?\n              * Is the issue reproducible in other terminals?\n      validations:\n        required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request\ndescription: Ask for a feature to be implemented and included in the next release\nlabels: feature request\ntitle: \"[REQUEST] \"\nbody:\n\n    - type: markdown\n      attributes:\n        value: |\n            Please make sure to [search issues](https://github.com/mawww/kakoune/issues?q=is%3Aissue) before creating a new one, to avoid duplicates and centralise conversations.\n\n            If you need to have a question about the editor itself (options, behaviour, code…) answered, make sure to check the [documentation](https://github.com/mawww/kakoune/tree/master/doc/pages) (also available via the `doc` command in the editor) or drop by [IRC](https://web.libera.chat/?channels=#kakoune) to get instant feedback.\n\n    - type: textarea\n      attributes:\n          label: \"Feature\"\n          description: \"What do you want implemented that is not already available in the development version?\"\n      validations:\n        required: true\n\n    - type: textarea\n      attributes:\n          label: \"Usecase\"\n          description: \"What use do you have for the requested feature?\"\n          placeholder: |\n              If the feature's usecase is self-evident, such as a hook or an option whose name hints at a functionality unequivocally, you may ignore this field.\n      validations:\n        required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.yml",
    "content": "name: Question\ndescription: Couldn't find an answer in the documentation? Ask away!\nlabels: question\ntitle: \"[QUESTION] \"\nbody:\n\n    - type: markdown\n      attributes:\n        value: |\n            Please make sure to [search issues](https://github.com/mawww/kakoune/issues?q=is%3Aissue) before creating a new one, to avoid duplicates and centralise conversations.\n\n            If you need to have a question about the editor itself (options, behaviour, code…) answered, make sure to check the [documentation](https://github.com/mawww/kakoune/tree/master/doc/pages) (also available via the `doc` command in the editor) or drop by [IRC](https://web.libera.chat/?channels=#kakoune) to get instant feedback.\n\n    - type: textarea\n      attributes:\n          label: \"Question\"\n          description: \"Couldn't find an answer in the documentation? Ask away!\"\n      validations:\n        required: true\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE/pull_request_template.md",
    "content": "<!--\nIf this is your first contribution to the Kakoune project, make sure to\ninclude an empty \"waiver\" commit to your Pull Request, as described in the\nfollowing document:\n\nhttps://github.com/mawww/kakoune/blob/master/CONTRIBUTING\n-->\n"
  },
  {
    "path": ".github/workflows/build-releases-linux.yaml",
    "content": "name: Build static binaries for Linux\n\non:\n  release:\n    types: [published, edited]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    container:\n      image: alpine:latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          ref: ${{ github.event.release.tag_name }}\n      - name: Prepare\n        run: apk add --no-cache binutils bzip2 g++ git make tar\n      - name: Build\n        run: |\n          mkdir -p kakoune-${{ github.event.release.tag_name }}-linux/\n          make -j$(nproc) PREFIX=$(pwd)/kakoune-${{ github.event.release.tag_name }}-linux static=yes install-strip\n          tar cvjf kakoune-${{ github.event.release.tag_name }}-linux.tar.bz2 kakoune-${{ github.event.release.tag_name }}-linux/\n      - name: Upload\n        uses: softprops/action-gh-release@v2\n        with:\n          files: kakoune-${{ github.event.release.tag_name }}-linux.tar.bz2\n"
  },
  {
    "path": ".github/workflows/makefile.yml",
    "content": "name: Makefile CI\non:\n  push:\n    branches: [ \"master\", \"main\" ]\n  pull_request:\n    branches: [ \"master\", \"main\" ]\njobs:\n  build:\n    timeout-minutes: 10\n    strategy:\n      matrix:\n        os: [ubuntu, macos]\n        toolchain: [gcc, llvm]\n        include:\n          - os: ubuntu\n            toolchain: gcc\n            install_cmd: |\n              sudo apt update\n              sudo apt install g++\n            CXX: g++\n          - os: ubuntu\n            toolchain: llvm\n            install_cmd: |\n              sudo apt update\n              sudo apt install clang\n            CXX: clang++\n          - os: macos\n            toolchain: gcc\n            install_cmd: |\n              brew update\n              brew install gcc\n            CXX: g++\n          - os: macos\n            toolchain: llvm\n            install_cmd: |\n              brew update\n              brew install llvm\n            CXX: clang++\n    runs-on: ${{ matrix.os }}-latest\n    steps:\n    - uses: actions/checkout@v3\n    - name: Install dependencies\n      run: ${{ matrix.install_cmd }}\n    - name: Build and test\n      run: make CXX=${{ matrix.CXX }} -j$(nproc 2>/dev/null || sysctl -n hw.ncpu) test\n"
  },
  {
    "path": ".gitignore",
    "content": "*.o\n*.d\n*.pyc\n*.1-r\n.*.kak.*\n*.tar*\nsrc/kak\nsrc/kak.debug\nsrc/kak.debug.*\nsrc/kak.opt\nsrc/kak.opt.*\nsrc/.version*\nsrc/.*.json\ndoc/kak.1.gz\ndoc/manpages/*.gz\ntags\nGPATH\nGRTAGS\nGTAGS\n"
  },
  {
    "path": "CONTRIBUTING",
    "content": "The preferred way to contribute would be through GitHub pull requests,\nas an alternative patches can be discussed on the IRC channel.\n\nWhen contributing your first changes, please include an empty commit for\ncopyright waiver using the following message (replace 'John Doe' with\nyour name or nickname):\n\n  John Doe Copyright Waiver\n\n  I dedicate any and all copyright interest in this software to the\n  public domain.  I make this dedication for the benefit of the public at\n  large and to the detriment of my heirs and successors.  I intend this\n  dedication to be an overt act of relinquishment in perpetuity of all\n  present and future rights to this software under copyright law.\n\nThe command to create an empty commit from the command-line is:\n\n  git commit --allow-empty\n"
  },
  {
    "path": "Makefile",
    "content": ".POSIX:\n.SUFFIXES:\n\nCXX = c++\n\ndebug = no\nstatic = no\ngzip_man = yes\n# to get format compatible with GitHub archive use \"gzip -S .gz\" here\ncompress_bin = bzip2\n\ntag-static-no=\ntag-static-yes=.static\n\ncompress-suffix-bzip2 = bz2\ncompress-suffix-zstd = zst\n\nCPPFLAGS-debug-yes = -DKAK_DEBUG\nCXXFLAGS-debug-yes = -O0 -g3\ntag-debug-yes = .debug\n\nCXXFLAGS-debug-no = -O3 -g3\ntag-debug-no = .opt\n\nCXXFLAGS-sanitize-address = -fsanitize=address\nLDFLAGS-sanitize-address = -lasan\ntag-sanitize-address = .san_a\n\nCXXFLAGS-sanitize-undefined = -fsanitize=undefined\n\nLDFLAGS-sanitize-undefined = -lasan -lubsan\ntag-sanitize-undefined = .san_u\n\nLDFLAGS-static-yes = -static -pthread\n\nversion = $(shell cat .version 2>/dev/null || git describe --tags HEAD 2>/dev/null | sed s/^v// || echo unknown)\nversion != cat .version 2>/dev/null || ( git describe --tags HEAD 2>/dev/null | sed s/^v// ) || echo unknown\n\nPREFIX = /usr/local\nDESTDIR = # root dir\n\nbindir = $(DESTDIR)$(PREFIX)/bin\nlibexecdir = $(DESTDIR)$(PREFIX)/libexec/kak\nsharedir = $(DESTDIR)$(PREFIX)/share/kak\ndocdir = $(DESTDIR)$(PREFIX)/share/doc/kak\nmandir = $(DESTDIR)$(PREFIX)/share/man/man1\n\n# Both Cygwin and MSYS2 have \"_NT\" in their uname.\nos = $(shell uname | sed 's/.*_NT.*/Windows/')\nos != uname | sed 's/.*_NT.*/Windows/'\n\nLIBS-os-Haiku = -lnetwork -lbe\n\nCPPFLAGS-os-OpenBSD = -DKAK_BIN_PATH=\\\"$(bindir)/kak\\\"\nmandir-os-OpenBSD = $(DESTDIR)$(PREFIX)/man/man1\n\nLDFLAGS-os-SunOS = -lsocket -rdynamic\n\nCPPFLAGS-os-Windows = -D_XOPEN_SOURCE=700\nLIBS-os-Windows = -ldbghelp\n\nCXXFLAGS-default = -std=c++2b -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-sign-compare\n\ncompiler = $(shell $(CXX) --version | grep -E -o 'clang|g\\+\\+|c\\+\\+' | head -1)\ncompiler != $(CXX) --version | grep -E -o 'clang|g\\+\\+|c\\+\\+' | head -1\nCXXFLAGS-compiler-clang = -fsized-deallocation\nCXXFLAGS-compiler-g++ = -Wno-init-list-lifetime -Wno-stringop-overflow\nCXXFLAGS-compiler-c++ = $(CXXFLAGS-compiler-g++)\n\nKAK_CPPFLAGS = \\\n\t$(CPPFLAGS-default) \\\n\t$(CPPFLAGS-debug-$(debug)) \\\n\t$(CPPFLAGS-os-$(os)) \\\n\t$(CPPFLAGS)\n\nKAK_CXXFLAGS = \\\n\t$(CXXFLAGS-default) \\\n\t$(CXXFLAGS-debug-$(debug)) \\\n\t$(CXXFLAGS-sanitize-$(sanitize)) \\\n\t$(CXXFLAGS-compiler-$(compiler)) \\\n\t$(CXXFLAGS)\n\nKAK_LDFLAGS = \\\n\t$(LDFLAGS-default) \\\n\t$(LDFLAGS-sanitize-$(sanitize)) \\\n\t$(LDFLAGS-static-$(static)) \\\n\t$(LDFLAGS-os-$(os)) \\\n\t$(LDFLAGS)\n\nKAK_LIBS = \\\n\t$(LIBS-os-$(os)) \\\n\t$(LIBS)\n\ntag = $(tag-debug-$(debug))$(tag-sanitize-$(sanitize))\ntagbin = $(tag)$(tag-static-$(static))\n\n.SUFFIXES: $(tag).o .cc\n.PHONY: src/kak\n\nsources = $(shell find src -type f -name '*.cc' | sed -e '/\\.version\\.cc/d')\nsources != find src -type f -name '*.cc' | sed -e '/\\.version\\.cc/d'\nobjects = $(sources:.cc=$(tag).o)\n\nall: src/kak\n\nsrc/kak: src/kak$(tagbin)\n\tln -sf kak$(tagbin) $@\n\nsrc/kak$(tagbin): src/.version$(tag).o $(objects)\n\t$(CXX) $(KAK_LDFLAGS) $(KAK_CXXFLAGS) $(objects) src/.version$(tag).o $(KAK_LIBS) -o $@\n\ndeps = $(shell touch src/.version$(tag).d && find src -type f -name '.*$(tag).d') # Ensure we find one deps for FreeBSD make\ndeps != touch src/.version$(tag).d && find src -type f -name '.*$(tag).d' # Ensure we find one deps for FreeBSD make\ninclude $(deps)\n\n.cc$(tag).o:\n\t$(CXX) $(KAK_CPPFLAGS) $(KAK_CXXFLAGS) -MD -MP -MF $(*D)/.$(*F)$(tag).d -c -o $@ $<\n\nsrc/.version.cc:\n\techo 'namespace Kakoune { const char *version = \"$(version)\"; }' > $@\n\nsrc/.version$(tag).o: src/.version.cc\n\t$(CXX) $(KAK_CPPFLAGS) $(KAK_CXXFLAGS) -c -o $@ src/.version.cc\n\n# Generate the man page\nman: gzip-man-$(gzip_man)\n\ngzip-man-yes: doc/kak.1.gz\ngzip-man-no: doc/kak.1\n\ndoc/kak.1.gz: doc/kak.1\n\tgzip -n -9 -f < doc/kak.1 > $@\n\ncheck: test\ntest: src/kak\n\tif [ $(os) = OpenBSD ]; then \\\n        \texport KAKOUNE_RUNTIME=$$PWD/share/kak; \\\n\tfi && \\\n\ttest/run\n\nTAGS: tags\ntags:\n\tctags -R\n\nclean:\n\trm -f $(objects) $(deps) src/.version*\n\ndist: kakoune-$(version).tar.$(compress-suffix-$(compress_bin))\n\nkakoune-$(version).tar.$(compress-suffix-$(compress_bin)): kakoune-$(version).tar\n\t$(compress_bin) -f $<\n\nkakoune-$(version).tar:\n\t@if ! [ -d .git ]; then echo \"make dist can only run from a git repo\";  false; fi\n\t@if git status -s | grep -qEv '^\\?\\?'; then echo \"working tree is not clean\";  false; fi\n\tgit archive --format=tar --prefix=$(@:.tar=)/ HEAD -o $@\n\techo \"$(version)\" > .version\n\ttar --transform \"s,^,$(@:.tar=)/,\" -rf $@ .version\n\trm -f .version\n\ndistclean: clean\n\trm -f src/kak src/kak$(suffix) src/.*.d src/*.o\n\tfind doc -type f -name '*.gz' -exec rm -f '{}' +\n\ninstalldirs: installdirs-debug-$(debug)\n\ninstalldirs-debug-no:\n\tmkdir -p \\\n\t\t$(bindir) \\\n\t\t$(libexecdir) \\\n\t\t$(sharedir)/rc \\\n\t\t$(sharedir)/colors \\\n\t\t$(sharedir)/doc \\\n\t\t$(docdir) \\\n\t\t$(mandir)\n\ninstalldirs-debug-yes: installdirs-debug-no\n\tmkdir -p $(sharedir)/gdb\n\ninstall: src/kak installdirs install-debug-$(debug) install-gzip-man-$(gzip_man)\n\tcp src/kak$(suffix) $(bindir)\n\tchmod 0755 $(bindir)/kak\n\n\tln -sf ../../bin/kak $(libexecdir)/kak\n\n\tcp share/kak/kakrc $(sharedir)\n\tchmod 0644 $(sharedir)/kakrc\n\n\tcp -r rc/* $(sharedir)/rc\n\tfind $(sharedir)/rc -type f -exec chmod 0644 {} +\n\t[ -e $(sharedir)/autoload ] || ln -s rc $(sharedir)/autoload\n\n\tcp colors/* $(sharedir)/colors\n\tchmod 0644 $(sharedir)/colors/*\n\n\tcp README.asciidoc $(docdir)\n\tchmod 0644 $(docdir)/*.asciidoc\n\tcp doc/pages/*.asciidoc $(sharedir)/doc\n\tchmod 0644 $(sharedir)/doc/*.asciidoc\n\ninstall-gzip-man-yes: gzip-man-yes installdirs\n\tcp -f doc/kak.1.gz $(mandir)\n\tchmod 0644 $(mandir)/kak.1.gz\n\ninstall-gzip-man-no: gzip-man-no installdirs\n\tcp -f doc/kak.1 $(mandir)\n\tchmod 0644 $(mandir)/kak.1\n\ninstall-debug-yes: installdirs-debug-yes\n\tcp -f gdb/kakoune.py $(sharedir)/gdb\n\tchmod 0644 $(sharedir)/gdb/kakoune.py\n\ninstall-debug-no: installdirs-debug-no\n\ninstall-strip: install\n\tstrip -s $(bindir)/kak\n\nuninstall:\n\trm -rf \\\n\t\t$(bindir)/kak \\\n\t\t$(libexecdir) \\\n\t\t$(sharedir) \\\n\t\t$(docdir) \\\n\t\t$(mandir)/kak.*\n"
  },
  {
    "path": "README.asciidoc",
    "content": "= image:{logo}[K,32,32,link=\"{website}\",title=\"Kakoune logo by p0nce\"] Kakoune image:{cirrus-img}[link=\"{cirrus-url}\"] image:{srht-img}[link=\"{srht-url}\"] image:{irc-img}[link=\"{irc-url}\"]\nifdef::env-github,env-browser[:outfilesuffix: .asciidoc]\n:logo: http://kakoune.org/img/kakoune_logo_32.png\n:website: https://kakoune.org\n:cirrus-img: https://api.cirrus-ci.com/github/mawww/kakoune.svg\n:cirrus-url: https://cirrus-ci.com/github/mawww/kakoune\n:srht-img: https://builds.sr.ht/~mawww/kakoune.svg\n:srht-url: https://builds.sr.ht/~mawww/kakoune?\n:irc-img: https://img.shields.io/badge/IRC-%23kakoune-blue.svg\n:irc-url: https://web.libera.chat/?channels=kakoune\n:icons: font\n:toc: right\n:pp: ++\n\nTL;DR\n-----\n\n{website}\n\n*Modal editor* -- *Faster as in fewer keystrokes* --\n*Multiple selections* -- *Orthogonal design*\n\n---------------------------------------------\ngit clone https://github.com/mawww/kakoune.git\ncd kakoune\nmake\n./src/kak\n---------------------------------------------\n\nSee https://github.com/mawww/golf for Kakoune solutions to vimgolf challenges,\nregularly beating the best Vim solution.\n\nSee the link:doc/design.asciidoc[design document] for more information on\nKakoune's philosophy and design.\n\nIntroduction\n------------\n\nKakoune is a code editor that implements Vi's \"keystrokes as a text editing\nlanguage\" model. As it is also a modal editor, it is somewhat similar to the\nVim editor (after which Kakoune was originally inspired).\n\nKakoune can operate in two modes: *normal* and *insertion*. In insertion mode,\nkeys are directly inserted into the current buffer. In normal mode, keys\nare used to manipulate the current selection and to enter insertion mode.\n\nKakoune has a strong focus on interactivity. Most commands provide immediate\nand incremental results, while being competitive with Vim in terms of keystroke count.\n\nKakoune works on selections, which are oriented, inclusive ranges of characters.\nSelections have an anchor and a cursor. Most commands move both of\nthem except when extending selections, where the anchor character stays fixed\nand the cursor moves around.\n\nSee https://vimeo.com/82711574\n\nJoin us on libera IRC `#Kakoune`\n\nFeatures\n~~~~~~~~\n\n * Multiple selections as a central way of interacting\n * Powerful selection manipulation primitives\n   - Select all regex matches in current selections\n   - Keep selections containing/not containing a match for a given regex\n   - Split current selections with a regex\n   - Text objects (paragraph, sentence, nestable blocks)\n * Powerful text manipulation primitives\n   - Align selections\n   - Rotate selection contents\n   - Case manipulation\n   - Indentation\n   - Piping each selection to external filter\n * Client-Server architecture\n   - Multiple clients on the same editing session\n   - Use tmux, zellij, kitty, wezterm or your X11/Wayland window manager to\n     manage splits/panes by spawning clients connected to the same session\n * Simple interaction with external programs\n * Automatic contextual help\n * Automatic as you type completion\n * Macros\n * Hooks\n * Syntax Highlighting\n   - Supports multiple languages in the same buffer\n   - Highlight a buffer differently in different windows\n\nScreenshots\n~~~~~~~~~~~\n\n[[screenshot-i3]]\n.Kakoune in i3\nimage::doc/screenshot-i3.gif[Kakoune in i3]\n\n[[screenshot-tmux]]\n.Kakoune in tmux\nimage::doc/screenshot-tmux.gif[Kakoune in tmux]\n\nGetting started\n---------------\n\nBuilding\n~~~~~~~~\n\nKakoune's dependencies are:\n\n * A {cpp}20 compliant compiler (GCC >= 10.3 or clang >= 11) along with its\n   associated {cpp} standard library (libstdc{pp} >= 10 or libc{pp})\n\nTo build, just type *make* in the root directory.\nTo generate man pages, type *make man* in the root directory.\n\nKakoune can be built on Linux, MacOS, and Cygwin. Due to Kakoune relying heavily\non being in a Unix-like environment, no native Windows version is planned.\n\nInstalling\n~~~~~~~~~~\n\nIn order to install *kak* on your system, rather than running it directly from\nits source directory, type *make install*. You can specify the `PREFIX` and\n`DESTDIR` if needed.\n\n[TIP]\n.Homebrew (macOS) or Linuxbrew\n====\n---------------------------------\nbrew install kakoune\n---------------------------------\n====\n\n[TIP]\n.MacPorts (macOS)\n====\n---------------------------------\nsudo port selfupdate\nsudo port install kakoune\n---------------------------------\n====\n\n[TIP]\n.Fedora supported versions and Rawhide\n====\n---------------------------------\ndnf install kakoune\n---------------------------------\n====\n\n[TIP]\n.Fedora daily builds\n====\nUse the https://copr.fedoraproject.org/coprs/jkonecny/kakoune/[copr] repository.\n---------------------------------\ndnf copr enable jkonecny/kakoune\ndnf install kakoune\n---------------------------------\n====\n\n[TIP]\n.RHEL/CentOS 8\n====\nKakoune can be found in the https://src.fedoraproject.org/rpms/kakoune/tree/epel8[EPEL8 repositories].\n---------------------------------\ndnf install kakoune\n---------------------------------\n====\n\n[TIP]\n.Arch Linux\n====\nKakoune is found in the https://www.archlinux.org/packages/community/x86_64/kakoune/[repositories].\n--------------------------------------------------\npacman -S kakoune\n--------------------------------------------------\n====\n\n[TIP]\n.Gentoo\n====\nKakoune is found in portage as\nhttps://packages.gentoo.org/packages/app-editors/kakoune[app-editors/kakoune].\n--------------------------------\nemerge kakoune\n--------------------------------\nhttps://wiki.gentoo.org/wiki/Kakoune[Installation and Gentoo specific documentation] is available.\n====\n\n[TIP]\n.Exherbo\n====\n--------------------------------\ncave resolve -x repository/mawww\ncave resolve -x kakoune\n--------------------------------\n====\n\n[TIP]\n.openSUSE\n====\nKakoune can be found in the https://software.opensuse.org/package/kakoune[repositories].\n\n---------------------------\nsudo zypper install kakoune\n---------------------------\n====\n\n[TIP]\n.Ubuntu\n====\nKakoune can be found in the Ubuntu repositories.\n\n----------------------------\nsudo apt install kakoune\n----------------------------\n\nIf you want to compile from source on 20.04 or earlier, you must force the build to use GCC 10, which is not the default. Also, make sure you have .local/bin in your path so that kak is available after the installation.\n\n----------------------------------------------------------------\ngit clone https://github.com/mawww/kakoune.git && cd kakoune/src\nCXX=g++-10 make\nmake PREFIX=$HOME/.local install\n----------------------------------------------------------------\n====\n\n[TIP]\n.Debian\n====\nKakoune can be found in Debian 9 (Stretch) and later releases.\n\n---------------------------\nsudo apt install kakoune\n---------------------------\n====\n\n[TIP]\n.FreeBSD\n====\nKakoune is available in the official ports tree as\nhttps://www.freshports.org/editors/kakoune[editors/kakoune].\n\nA binary package is also available and can be installed with\n--------------------------------------------------\npkg install kakoune\n--------------------------------------------------\n====\n\n[TIP]\n.OpenBSD\n====\nBuilding on OpenBSD 6.7 amd64.\n\n--------------------------------------------------\n# pkg_add git gmake\n# git clone https://github.com/mawww/kakoune\n# cd kakoune\n# export CXX=clang++\n# gmake install\n--------------------------------------------------\n\nKakoune is available in the 6.7-current port tree as \nhttp://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/editors/kakoune/[editors/kakoune]\n\nA binary package is also available for -current snapshot and can be installed with\n--------------------------------------------------\n# pkg_add kakoune\n--------------------------------------------------\n\nRunning with support for plugins.\n--------------------------------------------------\n# pkg_add dash\n$ export KAKOUNE_POSIX_SHELL=/usr/local/bin/dash \n$ kak\n--------------------------------------------------\n====\n\n[TIP]\n.Solus\n====\nKakoune is available in the Solus stable repository.\n\nIt can be installed with\n---------------------\neopkg install kakoune\n---------------------\n====\n\n[TIP]\n.Void\n====\nKakoune is available in the repositories.\n\nIt can be installed with\n-----------------------\nxbps-install -S kakoune\n-----------------------\n====\n\n[TIP]\n.Termux\n====\nKakoune is available in the repositories.\n\nIt can be installed with\n-------------------\npkg install kakoune\n-------------------\n====\n\n[TIP]\n.Nix\n====\n--------------------------------\nnix-env -iA nixpkgs.kakoune\n--------------------------------\n====\n\n[TIP]\n.pkgsrc (NetBSD, SmartOS, macOS, Linux, etc)\n====\n--------------------------------\npkg_add kakoune\n--------------------------------\n====\nhttps://repology.org/project/kakoune/versions[image:https://repology.org/badge/vertical-allrepos/kakoune.svg[Packaging status]]\n\nRunning\n~~~~~~~\n\nRunning *kak* launches a new kak session with a client on local terminal.\nRun *kak -help* to discover the valid command line flags.\n\nConfiguration\n^^^^^^^^^^^^^\n\nThere are two directories containing Kakoune's scripts:\n\n* `runtime`: located in `../share/kak/` relative to the `kak` binary\n  contains the system scripts, installed with Kakoune.\n* `userconf`: located in `$XDG_CONFIG_HOME/kak/`, which will fallback\n  to `$HOME/.config/kak/` if `$XDG_CONFIG_HOME` is not set, containing\n  the user configuration.\n\nUnless `-n` is specified, Kakoune will load its startup script located\nat `${runtime}/kakrc` relative to the `kak` binary. This startup script\nis responsible for loading the user configuration.\n\nFirst, Kakoune will search recursively for `.kak` files in the `autoload`\ndirectory. It will first look for an `autoload` directory at\n`${userconf}/autoload` and will fallback to `${runtime}/autoload` if\nit does not exist.\n\nOnce all those files are loaded, Kakoune will try to source\n`${runtime}/kakrc.local`, which is expected to contain distribution provided\nconfiguration.\n\nFinally, the user configuration will load from `${userconf}/kakrc`.\n\nNOTE: If you create a user `autoload` directory in `${userconf}/autoload`,\nthe system one at `${runtime}/autoload` will not load anymore. You can\nadd a symbolic link to it (or to individual scripts) inside\n`${userconf}/autoload` to keep loading system scripts.\n\nBasic Interaction\n-----------------\n\nSelections\n~~~~~~~~~~\n\nThe main concept in Kakoune is the selection. A selection is an inclusive,\ndirected range of characters. A selection has two ends, the anchor and the\ncursor.\n\nThere is always at least one selection, and a selection is always at least\none character (in which case the anchor and cursor of the selection are\non the same character).\n\nNormal Mode\n~~~~~~~~~~~\n\nIn normal mode, keys are not inserted directly inside the buffer, but are editing\ncommands. These commands provide ways to manipulate either the selections themselves\nor the selected text.\n\nInsert Mode\n~~~~~~~~~~~\n\nWhen entering insert mode, keys are now directly inserted before each\nselection's cursor. Some additional keys are recognised in insert mode:\n\n * `<esc>`: leave insert mode\n * `<backspace>`: delete characters before cursors\n * `<del>`: delete characters under cursors\n * `<left>, <right>, <up>, <down>`: move cursors in given direction\n * `<home>`: move cursors to line beginning\n * `<end>`: move cursors to line ending\n\n * `<c-n>`: select next completion candidate\n * `<c-p>`: select previous completion candidate\n * `<c-x>`: explicit insert completion query, followed by:\n   - `f`: explicit file completion\n   - `w`: explicit word completion\n   - `l`: explicit line completion\n * `<c-o>`: disable automatic completion for this insert session\n\n * `<c-r>`: insert contents of the register given by next key\n * `<c-v>`: insert next keystroke directly into the buffer,\n    without interpreting it\n\n * `<c-u>`: commit changes up to now as a single undo group\n\n * `<a-;>`: escape to normal mode for a single command\n\nMovement\n~~~~~~~~\n\nSee <<Appending>> below for instructions on extending (appending to) the current selection in order to select more text.\n\n * `h`: select the character on the left of selection end\n * `j`: select the character below the selection end\n * `k`: select the character above the selection end\n * `l`: select the character on the right of selection end\n\n * `w`: select the word and following whitespaces on the right of selection end\n * `b`: select preceding whitespaces and the word on the left of selection end\n * `e`: select preceding whitespaces and the word on the right of selection end\n * `<a-[wbe]>`: same as [wbe], but select WORD instead of word\n\n * `f`: select to (including) the next occurrence of the given character\n * `t`: select until (excluding) the next occurrence of the given character\n * `<a-[ft]>`: same as [ft] but in the other direction\n\n * `m`: select to matching character\n * `M`: extend selection to matching character\n\n * `x`: expand selections to contain full lines (including end-of-lines)\n * `<a-x>`: trim selections to only contain full lines (not including last\n            end-of-line)\n\n * `%`: select whole buffer\n\n * `<a-h>`: select to line begin\n * `<a-l>`: select to line end\n\n * `/`: search (select next match)\n * `<a-/>`: search (select previous match)\n * `?`: search (extend to next match)\n * `<a-?>`: search (extend to previous match)\n * `n`: select next match\n * `N`: add a new selection with next match\n * `<a-n>`: select previous match\n * `<a-N>`: add a new selection with previous match\n\n * `pageup, <c-b>`: scroll one page up\n * `pagedown, <c-f>`: scroll one page down\n * `<c-u>`: scroll half a page up\n * `<c-d>`: scroll half a page down\n\n * `)`: rotate selections (the main selection becomes the next one)\n * `(`: rotate selections backwards\n\n * `;`: reduce selections to their cursor\n * `<a-;>`: flip the selections' direction\n * `<a-:>`: ensure selections are in forward direction (cursor after anchor)\n\n * `<a-.>`: repeat last object or `f`/`t` selection command.\n\n * `_`: trim selections\n\nA word is a sequence of alphanumeric characters or underscore, a WORD is a\nsequence of non whitespace characters.\n\nAppending\n~~~~~~~~~\n\nFor most <<Movement>> commands, using `Shift` extends the current selection\ninstead of replacing it.\n\nExamples:\n\n * `wWW` selects 3 consecutive words: first `w` selects a word, then `WW` extends the selection two words further.\n * `f/F/` selects up to and including the second `/` character forward.\n\nUsing Counts\n~~~~~~~~~~~~\n\nMost selection commands also support counts, which are entered before the\ncommand itself.\n\nFor example, `3W` selects 3 consecutive words and `3w` select the third word on\nthe right of selection end.\n\nDisabling Hooks\n~~~~~~~~~~~~~~~\n\nAny normal mode command can be prefixed with `\\` which will disable hook execution\nfor the duration for the command (including the duration of modes the command could\nmove to, so `\\i` will disable hooks for the whole insert session).\n\nAs autoindentation is implemented in terms of hooks, this can be used to disable\nit when pasting text.\n\nChanges\n~~~~~~~\n\n * `i`: enter insert mode before each selection\n * `a`: enter insert mode after each selection\n * `d`: yank and delete each selection\n * `c`: yank and delete each selection and enter insert mode\n * `.`: repeat last insert mode change (`i`, `a`, or `c`, including\n        the inserted text)\n\n * `<a-d>`: delete each selection\n * `<a-c>`: delete each selection and enter insert mode\n\n * `I`: enter insert mode at each selection begin line start\n * `A`: enter insert mode at each selection end line end\n * `o`: enter insert mode in one (or given count) new lines below\n        each selection end\n * `O`: enter insert mode in one (or given count)  new lines above\n        each selection begin\n\n * `<a-o>`: add an empty line below each cursor\n * `<a-O>`: add an empty line above each cursor\n\n * `y`: yank selections\n * `p`: paste after each selection end\n * `P`: paste before each selection begin\n * `<a-p>`: paste all after each selection end\n * `<a-P>`: paste all before each selection begin\n * `R`: replace each selection with yanked text\n * `<a-R>`: replace each selection with every yanked text\n\n * `r`: replace each character with the next entered one\n\n * `<a-j>`: join selected lines\n * `<a-J>`: join selected lines and select spaces inserted\n            in place of line breaks\n * `<a-_>`: merge contiguous selections together (works across lines as well)\n\n * `<gt> (>)`: indent selected lines\n * `<a-gt>`: indent selected lines, including empty lines\n * `<lt> (<)`: deindent selected lines\n * `<a-lt>`: deindent selected lines, do not remove incomplete\n        indent (3 leading spaces when indent is 4)\n\n * `|`: pipe each selection through the given external filter program\n        and replace the selection with its output.\n * `<a-|>`: pipe each selection through the given external filter program\n        and ignore its output\n\n * `!`: insert command output before each selection\n * `<a-!>`: append command output after each selection\n\n * `u`: undo last change\n * `<c-k>`: move backward in history\n * `<a-u>`: undo selection changes\n * `U`: redo last change\n * `<c-j>`: move forward in history\n * `<a-U>`: redo selection changes\n\n * `&`: align selections, align the cursor of selections by inserting\n        spaces before the first character of the selection\n * `<a-&>`: copy indent, copy the indentation of the main selection\n        (or the count one if a count is given) to all other ones\n\n * ```: to lower case\n * `~`: to upper case\n * ``<a-`>``: swap case\n\n * `@`: convert selected tabs to spaces, uses the buffer tabstop option or\n        the count parameter for tabstop.\n * `<a-@>`: convert selected spaces to tabs, uses the buffer tabstop option\n            or the count parameter for tabstop.\n\n * `<a-)>`: rotate selections content, if specified, the count groups\n            selections, so `3<a-)>` rotate (1, 2, 3) and (4, 5, 6)\n            independently.\n * `<a-(>`: rotate selections content backwards\n\nGoto Commands\n~~~~~~~~~~~~~\n\nCommands beginning with `g` are used to goto certain position and or buffer.\nIf a count is given prior to hitting `g`, `g` will jump to the given line.\nUsing `G` will extend the selection rather than jump.\n\nSee <<doc/pages/keys#goto-commands,`:doc keys goto-commands`>>.\n\nView commands\n~~~~~~~~~~~~~\n\nCommands beginning with `v` permit to center or scroll the current\nview. Using `V` will lock view mode until `<esc>` is hit\n\nSee <<doc/pages/keys#view-commands,`:doc keys view-commands`>>.\n\nMarks\n~~~~~\n\nCurrent selections position can be saved in a register and restored later on.\n\nSee <<doc/pages/keys#marks,`:doc keys marks`>>.\n\nJump list\n~~~~~~~~~\n\nSome commands, like the goto commands, buffer switch or search commands,\npush the previous selections to the client's jump list.\n\nSee <<doc/pages/keys#jump-list,`:doc keys jump-list`>>.\n\nMulti Selection\n~~~~~~~~~~~~~~~\n\nKak was designed from the start to handle multiple selections.\nOne way to get a multiselection is via the `s` key.\n\nFor example, to change all occurrences of word 'roger' to word 'marcel'\nin a paragraph, here is what can be done:\n\n * select the paragraph with `x` then enough `J`\n * press `s` and enter roger, then enter\n * now paragraph selection was replaced with multiselection of each roger in\nthe paragraph\n * press `c` and marcel<esc> to replace rogers with marcels\n\nA multiselection can also be obtained with `S`, which splits the current\nselection according to the regex entered. To split a comma separated list,\nuse `S` then ', *'\n\nThe regex syntax supported by Kakoune is the based on the ECMAScript script\nsyntax and is described at <<doc/pages/regex#,`:doc regex`>>.\n\n`s` and `S` share the search pattern with `/`, and hence entering an empty\npattern uses the last one.\n\nAs a convenience, `<a-s>` allows you to split the current selections on\nline boundaries.\n\nTo clear multiple selections, use `,`. To keep only the nth selection\nuse `n` followed by `,`, in order to remove a selection, use `<a-,>`.\n\n`<a-k>` allows you to enter a regex and keep only the selections that\ncontains a match for this regex. Using `<a-K>` you can keep the selections\nnot containing a match.\n\n`C` duplicates selections on the lines that follow them, column-wise.\n`<a-C>` does the same but on the preceding lines.\n\n`$` allows you to enter a shell command and pipe each selection to it.\nSelections whose shell command returns 0 will be kept, other will be dropped.\n\nObject Selection\n~~~~~~~~~~~~~~~~\n\nObjects are specific portions of text, like sentences, paragraphs, numbers…\nKakoune offers many keys allowing you to select various text objects.\n\nSee <<doc/pages/keys#object-selection,`:doc keys object-selection`>>.\n\nCommands\n--------\n\nWhen pressing `:` in normal mode, Kakoune will open a prompt to enter a command.\n\nCommands are used for non editing tasks, such as opening a buffer, writing the\ncurrent one, quitting, etc.\n\nSee <<doc/pages/keys#prompt-commands,`:doc keys prompt-commands`>>.\n\nBasic Commands\n~~~~~~~~~~~~~~\n\nSome commands take an exclamation mark (`!`), which can be used to force\nthe execution of the command (i.e. to quit a modified buffer, the\ncommand `q!` has to be used).\n\nCommands starting with horizontal whitespace (e.g. a space) will not be\nsaved in the command history.\n\n * `cd [<directory>]`: change the current directory to `<directory>`, or the home directory if unspecified\n * `doc <topic>`: display documentation about a topic. The completion list\n     displays the available topics.\n * `e[dit][!] <filename> [<line> [<column>]]`: open buffer on file, go to given\n     line and column. If file is already opened, just switch to this file.\n     Use edit! to force reloading.\n * `w[rite][!] [<filename>]`: write buffer to <filename> or use its name if\n     filename is not given. If the file is write-protected, its\n     permissions are temporarily changed to allow saving the buffer and\n     restored afterwards when the write! command is used.\n * `w[rite]a[ll]`: write all buffers that are associated to a file.\n * `q[uit][!] [<exit status>]`: exit Kakoune, use quit! to force quitting even\n     if there is some unsaved buffers remaining. If specified, the client exit\n     status will be set to <exit status>.\n * `w[a]q[!] [<exit status>]`: write the current buffer (or all buffers when\n     `waq` is used) and quit. If specified, the client exit status will be set\n     to <exit status>.\n * `kill[!]`: terminate the current session, all the clients as well as the server,\n     use kill! to ignore unsaved buffers\n * `b[uffer] <name>`: switch to buffer <name>\n * `b[uffer]n[ext]`: switch to the next buffer\n * `b[uffer]p[rev]`: switch to the previous buffer\n * `d[el]b[uf][!] [<name>]`: delete the buffer <name>\n * `source <filename>`: execute commands in <filename>\n * `colorscheme <name>`: load named colorscheme.\n * `rename-client <name>`: set current client name\n * `rename-buffer <name>`: set current buffer name\n * `rename-session <name>`: set current session name\n * `echo [options] <text>`: show <text> in status line, with the following options:\n   ** `-markup`: expand the markup strings in <text>\n   ** `-debug`: print the given text to the `\\*debug*` buffer\n * `nop`: does nothing, but as with every other commands, arguments may be\n     evaluated. So nop can be used for example to execute a shell command\n     while being sure that its output will not be interpreted by kak.\n     `:%sh{ echo echo tchou }` will echo tchou in Kakoune, whereas\n     `:nop %sh{ echo echo tchou }` will not, but both will execute the\n     shell command.\n * `fail <text>`: raise an error, uses <text> as its description\n\nMultiple commands\n~~~~~~~~~~~~~~~~~\n\nMultiple commands can be separated either by new lines or by semicolons,\nas such a semicolon must be escaped with `\\;` to be considered as a literal\nsemicolon argument.\n\nString syntax and expansions\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nValues, options and shell context can be interpolated in strings.\n\nSee <<doc/pages/expansions#,`:doc expansions`>>.\n\nConfiguration & Autoloading\n---------------------------\n\nKakrc\n~~~~~\n\nIf not launched with the `-n` switch, Kakoune will source the\n`../share/kak/kakrc` file relative to the `kak` binary, which\nwill source additional files:\n\nIf the `$XDG_CONFIG_HOME/kak/autoload` directory exists, load every\n`*.kak` files in it, and load recursively any subdirectory.\n\nIf it does not exist, falls back to the site wide autoload directory\nin `../share/kak/autoload/`.\n\nAfter that, if it exists, source the `$XDG_CONFIG_HOME/kak/kakrc` file\nwhich should be used for user configuration.\n\nIn order to continue autoloading site-wide files with a local autoload\ndirectory, just add a symbolic link to `../share/kak/autoload/` into\nyour local autoload directory.\n\nColor Schemes\n~~~~~~~~~~~~~\n\nKakoune ships with some color schemes that are installed to\n`../share/kak/colors/`. If `$XDG_CONFIG_HOME/kak/colors/` is present\nthe builtin command `colorscheme` will offer completion for those\ncolor schemes. If a scheme is duplicated in userspace, it will take\nprecedence.\n\nOptions\n-------\n\nKakoune can store named and typed values that can be used both to\ncustomize the core editor behaviour, and to keep data used by extension\nscripts.\n\nSee <<doc/pages/options#,`:doc options`>>.\n\n\nAdvanced topics\n---------------\n\nFaces\n~~~~~\n\nFaces describe how characters are displayed on the screen: color, bold, italic...\n\nSee <<doc/pages/faces#,`:doc faces`>>.\n\nRegisters\n~~~~~~~~~\n\nRegisters are named lists of text. They are used for various purposes,\nlike storing the last yanked text, or the captured groups associated with the selections.\n\nSee <<doc/pages/registers#,`:doc registers`>>.\n\nMacros\n~~~~~~\n\nKakoune can record and replay a sequence of key presses.\n\nSee <<doc/pages/keys#macros,`:doc keys macros`>>.\n\nSearch selection\n~~~~~~~~~~~~~~~~\n\nUsing the `*` key, you can set the search pattern to the current selection.\nSee <<doc/pages/keys#searching,`:doc keys searching`>>.\n\nRegex syntax\n~~~~~~~~~~~~\n\nKakoune regex syntax is based on the ECMAScript syntax (ECMA-262 standard).\nIt always runs on Unicode codepoint sequences, not on bytes.\n\nSee <<doc/pages/regex#,`:doc regex`>>.\n\nExec and Eval\n~~~~~~~~~~~~~\n\nThe `execute-keys` and `evaluate-commands` are useful for scripting\nin non interactive contexts.\n\nSee <<doc/pages/execeval#,`:doc execeval`>>.\n\nInsert mode completion\n~~~~~~~~~~~~~~~~~~~~~~\n\nKakoune can propose completions while inserting text: filenames, words, lines…\n\nSee <<doc/pages/keys#insert-mode-completion,`:doc keys insert-mode-completion`>>.\n\nEscape to normal mode\n~~~~~~~~~~~~~~~~~~~~~\n\nFrom insert mode, pressing `<a-;>` allows you to execute a single normal mode\ncommand. This provides a few advantages:\n\n * The selections are not modified: when leaving insert mode using `<esc>` the\n   selections can change, for example when insert mode was entered with `a` the\n   cursor will go back one char. Or if on an end of line the cursor will go back\n   left (if possible).\n\n * The modes are nested: that means the normal mode can enter prompt (with `:`),\n   or any other modes (using `:on-key` or `:menu` for example), and these modes\n   will get back to the insert mode afterwards.\n\nThis feature is tailored for scripting/macros, as it provides a more predictable\nbehaviour than leaving insert mode with `<esc>`, executing normal mode command\nand entering back insert mode (with which binding ?)\n\nSee <<doc/pages/modes#,`:doc modes`>>.\n\nHighlighters\n~~~~~~~~~~~~\n\nManipulation of the displayed text, such as syntax coloration and wrapping\nis done through highlighters.\n\nSee <<doc/pages/highlighters#,`:doc highlighters`>>.\n\nHooks\n~~~~~\n\nCommands can be registered to be executed when certain events arise with hooks.\n\nSee <<doc/pages/hooks#,`:doc hooks`>>.\n\nKey Mapping\n~~~~~~~~~~~\n\nCustom key shortcuts can be registered through mappings.\n\nSee <<doc/pages/mapping#,`:doc mapping`>>.\n\nDefining Commands and Aliases\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nNew commands can be created using `:define-command`.\n\nSee <<doc/pages/commands#declaring-new-commands,`:doc commands declaring-new-commands`>>.\n\nThey can be given additional short names depending of the scope with `:alias`.\n\nSee <<doc/pages/commands#aliases,`:doc commands aliases`>>.\n\nSome helper commands are available to define composite commands.\n\nSee <<doc/pages/commands#helpers,`:doc commands helpers`>>.\n\nFIFO Buffers\n~~~~~~~~~~~\n\nFIFO buffers are very useful for running some commands asynchronously while\nprogressively displaying their result in Kakoune.\n\nSee <<doc/pages/buffers#fifo-buffers,`:doc buffers fifo-buffers`>>.\n\nCredits\n-------\n\nThanks to https://github.com/p0nce[p0nce] for designing the\nhttps://github.com/mawww/kakoune/blob/master/doc/kakoune_logo.svg[Kakoune\nlogo].\n\nAnd thanks to all the\nhttps://github.com/mawww/kakoune/graphs/contributors[contributors] who help\nmove the project forward!\n"
  },
  {
    "path": "UNLICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\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 BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <https://unlicense.org/>\n"
  },
  {
    "path": "VIMTOKAK",
    "content": "Vi(m) to Kakoune:\n=================\n\nKakoune is inspired heavily by Vim. It strives to be as efficient as Vim,\nmore consistent and simpler. A big difference is that a lot of special\nfeatures in Vim just become regular interactions of basic features in\nKakoune.\n\nOperations and moves are reversed in Kakoune. First select whatever text\nyou want to operate on, and then use a modifying operation. That makes\nthings more consistent: Vim needs separate x and d operations because\nof the operator -> move order, while Kakoune only needs the d operation.\nSelecting first also allows more complex selections.\n\ndelete a word:\n * vim: dw\n * kak: wd\n\ndelete a character:\n * vim: x\n * kak: d or ;d (; reduces the selection to a single char)\n\ncopy a line:\n * vim: yy\n * kak: xy\n\nglobal replace:\n * vim: :%s/word/replacement<ret>\n * kak: %sword<ret>creplacement<esc>,\n\nExplanation: '%' selects the entire buffer, 's' opens a prompt for a\nregex, <ret> validates the regex and replaces the selection with one\nper match (hence all occurences of \"word\" are selected). 'c' deletes\nthe selection contents and enters insert mode where \"replacement\" is\ntyped, and <esc> goes back to normal mode. The final ',' gets rid of\nmultiple cursors.\n\nNote that the Kakoune version is one key less, and is not a special\nfeature per se, but just a nice way Kakoune features work together.\n\nglobal interactive replace:\n * vim: :%s/word/replacement/gc<ret>\n   and then keep pressing 'y' to accept the change or 'n' to reject.\n * kak: /word<ret>creplacement<esc>\n   and then press 'n' to search for the next occurence and either '.'\n   to redo the last insert operation (that is replace 'word' with\n   'replacement') or 'n' to go to the next match.\n\nreplace in current curly brace block:\n * vim: viB:s/word/replacement<ret>\n * kak: <a-i>Bsword<ret>creplacement<esc>\n\nHere again, Vim had to rely on a special feature, visual mode.\n\njoin line with next:\n * vim: J\n * kak: <a-J>\n\ndelete to line end:\n * vim: d$\n * kak: <a-l>d or Gld\n\nSome classic Vim moves are not bound to the same key. Kakoune\nuses shifted moves to extend the selection, so Vim moves that were bound to\nshifted characters had to change.\n\n* % became m (for \"matching\"). However, m replaces the selection with the next\n  block. If you want to get a selection from the current point to the next\n  block's end, you should use ;M (; reduces the selection to one character).\n\n* 0 and $ became <a-h> and <a-l>. Equivalent bindings are gh and gl.\n\n:[gv]/re/cmd\nTo emulate :g or :v, use % to select the whole buffer, <a-s> to get\none selection per line, and then <a-k> or <a-K> to keep only the\nselections matching (or not matching) the entered regex.\n"
  },
  {
    "path": "colors/base16.kak",
    "content": "##\n## base16.kak by lenormf\n##\n\nevaluate-commands %sh{\n    black_lighterer='rgb:383838'\n    black_lighter='rgb:2D2D2D'\n    black_light='rgb:1C1C1C'\n    cyan_light='rgb:7CB0FF'\n    green_dark='rgb:A1B56C'\n    grey_dark='rgb:585858'\n    grey_light='rgb:D8D8D8'\n    magenta_dark='rgb:AB4642'\n    magenta_light='rgb:AB4434'\n    orange_dark='rgb:DC9656'\n    orange_light='rgb:F7CA88'\n    purple_dark='rgb:BA8BAF'\n\n    ## code\n    echo \"\n        face global value ${orange_dark}+b\n        face global type ${orange_light}\n        face global variable ${magenta_dark}\n        face global module ${green_dark}\n        face global function ${cyan_light}\n        face global string ${green_dark}\n        face global keyword ${purple_dark}+b\n        face global operator ${cyan_light}\n        face global attribute ${orange_dark}\n        face global comment ${grey_dark}\n        face global documentation comment\n        face global meta ${orange_light}\n        face global builtin default+b\n    \"\n\n    ## markup\n    echo \"\n        face global title blue\n        face global header ${cyan_light}\n        face global mono ${green_dark}\n        face global block ${orange_dark}\n        face global link blue\n        face global bullet ${magenta_light}\n        face global list ${magenta_dark}\n    \"\n\n    ## builtin\n    echo \"\n        face global Default ${grey_light},${black_lighter}\n        face global PrimarySelection white,blue+fg\n        face global SecondarySelection black,blue+fg\n        face global PrimaryCursor black,white+fg\n        face global SecondaryCursor black,white+fg\n        face global PrimaryCursorEol black,${cyan_light}+fg\n        face global SecondaryCursorEol black,${cyan_light}+fg\n        face global LineNumbers ${grey_light},${black_lighter}\n        face global LineNumberCursor ${grey_light},rgb:282828+b\n        face global MenuForeground ${grey_light},blue\n        face global MenuBackground blue,${grey_light}\n        face global MenuInfo ${cyan_light}\n        face global Information ${black_light},${cyan_light}\n        face global Error ${grey_light},${magenta_light}\n        face global DiagnosticError ${magenta_light}\n        face global DiagnosticWarning ${cyan_light}\n        face global StatusLine ${grey_light},${black_lighterer}\n        face global StatusLineMode ${orange_dark}\n        face global StatusLineInfo ${cyan_light}\n        face global StatusLineValue ${green_dark}\n        face global StatusCursor ${black_lighterer},${cyan_light}\n        face global Prompt ${black_light},${cyan_light}\n        face global MatchingChar ${cyan_light},${black_light}+b\n        face global BufferPadding ${cyan_light},${black_lighter}\n        face global Whitespace ${grey_dark}+f\n    \"\n}\n"
  },
  {
    "path": "colors/black-on-white.kak",
    "content": "# Black-on-bright-white colorscheme for minimal distraction & maximal contrast.\n# Works well with e-ink screens.\n\n# For Code\nface global value black\nface global type black\nface global variable black\nface global module black\nface global function black\nface global string black\nface global keyword black\nface global operator black\nface global attribute black\nface global comment black\nface global documentation comment\nface global meta black\nface global builtin black\n\n# For markup\nface global title black\nface global header black\nface global mono black\nface global block black\nface global link black\nface global bullet black\nface global list black\n\n# builtin faces\nface global Default black,bright-white\nface global PrimarySelection black,rgb:cccccc+fg\nface global SecondarySelection black,rgb:e0e0e0+fg\nface global PrimaryCursor bright-white,black+fg\nface global SecondaryCursor bright-white,rgb:777777+fg\nface global PrimaryCursorEol black,rgb:777777+fg\nface global SecondaryCursorEol black,rgb:aaaaaa+fg\nface global LineNumbers black,bright-white\nface global LineNumberCursor bright-white,black\nface global MenuForeground bright-white,black+fg\nface global MenuBackground black,rgb:e0e0e0+fg\nface global MenuInfo black # Overridden by MenuForeground and MenuBackground\nface global Information black,rgb:e0e0e0\nface global Error bright-white,black\nface global DiagnosticError black\nface global DiagnosticWarning black\nface global StatusLine black,bright-white\nface global StatusLineMode black,bright-white\nface global StatusLineInfo black,bright-white\nface global StatusLineValue black,bright-white\nface global StatusCursor bright-white,black\nface global Prompt bright-white,black\nface global MatchingChar black,bright-white+b\nface global Whitespace black,bright-white+fd\nface global BufferPadding black,bright-white\n"
  },
  {
    "path": "colors/default.kak",
    "content": "# Kakoune default color scheme\n\n# For Code\nface global value red\nface global type yellow\nface global variable green\nface global module green\nface global function cyan\nface global string magenta\nface global keyword blue\nface global operator yellow\nface global attribute green\nface global comment cyan\nface global documentation comment\nface global meta magenta\nface global builtin default+b\n\n# For markup\nface global title blue\nface global header cyan\nface global mono green\nface global block magenta\nface global link cyan\nface global bullet cyan\nface global list yellow\n\n# builtin faces\nface global Default default,default\nface global PrimarySelection white,blue+fg\nface global SecondarySelection black,blue+fg\nface global PrimaryCursor black,white+fg\nface global SecondaryCursor black,white+fg\nface global PrimaryCursorEol black,cyan+fg\nface global SecondaryCursorEol black,cyan+fg\nface global LineNumbers default,default\nface global LineNumberCursor default,default+r\nface global MenuForeground white,blue\nface global MenuBackground blue,white\nface global MenuInfo cyan\nface global Information black,yellow\nface global Error black,red\nface global DiagnosticError red\nface global DiagnosticWarning yellow\nface global StatusLine cyan,default\nface global StatusLineMode yellow,default\nface global StatusLineInfo blue,default\nface global StatusLineValue green,default\nface global StatusCursor black,cyan\nface global Prompt yellow,default\nface global MatchingChar default,default+b\nface global Whitespace default,default+fd\nface global BufferPadding blue,default\n"
  },
  {
    "path": "colors/desertex.kak",
    "content": "# desertex theme\n\n# Code\nface global value         rgb:fa8072\nface global type          rgb:dfdfbf\nface global identifier    rgb:87ceeb\nface global string        rgb:fa8072\nface global error         rgb:c3bf9f+b\nface global keyword       rgb:eedc82\nface global operator      rgb:87ceeb\nface global attribute     rgb:eedc82\nface global comment       rgb:7ccd7c+i\nface global documentation comment\n\n# #include <...>\nface global meta rgb:ee799f\n\n# Markup\nface global title  blue\nface global header cyan\nface global mono   green\nface global block  magenta\nface global link   cyan\nface global bullet cyan\nface global list   yellow\n\n# Builtin\n# fg,bg+attributes\n# face global Default default,rgb:262626 <- change the terminal bg color instead\nface global Default default,default\n\nface global PrimarySelection   white,blue+fg\nface global SecondarySelection black,blue+fg\n\nface global PrimaryCursor   black,white+fg\nface global SecondaryCursor black,white+fg\n\nface global PrimaryCursorEol   black,rgb:7ccd7c+fg\nface global SecondaryCursorEol black,rgb:7ccd7c+fg\n\nface global LineNumbers      rgb:605958\nface global LineNumberCursor yellow,default+b\n\n# Bottom menu:\n# text + background\nface global MenuBackground black,rgb:c2bfa5+b\n# selected entry in the menu (use 302028 when true color support is fixed)\nface global MenuForeground rgb:f0a0c0,magenta\n\n# completion menu info\nface global MenuInfo white,rgb:445599\n\n# assistant, [+]\nface global Information black,yellow\n\nface global Error      white,red\nface global DiagnosticError red\nface global DiagnosticWarning yellow\nface global StatusLine cyan,default\n\n# Status line modes and prompts:\n# insert, prompt, enter key...\nface global StatusLineMode rgb:ffd75f,default\n\n# 1 sel\nface global StatusLineInfo blue,default\n\n# param=value, reg=value. ex: \"ey\nface global StatusLineValue green,default\n\nface global StatusCursor black,cyan\n\n# :\nface global Prompt blue\n\n# (), {}\nface global MatchingChar cyan+b\n\n# EOF tildas (~)\nface global BufferPadding blue,default\n\n# Whitespace characters\nface global Whitespace default+f\n"
  },
  {
    "path": "colors/github.kak",
    "content": "##\n## github.kak by lenormf\n## v1.0\n##\n\n## code\nface global value rgb:0086B3+b\nface global type rgb:795DA3\nface global variable rgb:0086B3\nface global module rgb:0086B3\nface global function rgb:A71D5D\nface global string rgb:183691\nface global keyword rgb:A71D5D+b\nface global operator yellow\nface global attribute rgb:A71D5D\nface global comment rgb:AAAAAA\nface global documentation comment\nface global meta rgb:183691\nface global builtin default+b\n\n## markup\nface global title blue\nface global header cyan\nface global mono green\nface global block magenta\nface global link cyan\nface global bullet cyan\nface global list yellow\n\n## builtin\nface global Default rgb:121213,rgb:F8F8FF\nface global PrimarySelection rgb:121213,rgb:A6F3A6+fg\nface global SecondarySelection rgb:121213,rgb:DBFFDB+fg\nface global PrimaryCursor rgb:121213,rgb:888888+fg\nface global SecondaryCursor rgb:121213,rgb:AAAAAA+fg\nface global PrimaryCursorEol rgb:121213,rgb:A71D5D+fg\nface global SecondaryCursorEol rgb:121213,rgb:A71D5D+fg\nface global LineNumbers rgb:A0A0A0,rgb:ECECEC\nface global LineNumberCursor rgb:434343,rgb:DDDDDD\nface global MenuForeground rgb:434343,rgb:CDCDFD\nface global MenuBackground rgb:F8F8FF,rgb:808080\nface global Information rgb:F8F8FF,rgb:4078C0\nface global Error rgb:F8F8FF,rgb:BD2C00\nface global DiagnosticError rgb:CF222E\nface global DiagnosticWarning rgb:9A6700\nface global StatusLine rgb:434343,rgb:DDDDDD\nface global StatusCursor rgb:434343,rgb:CDCDFD\nface global Prompt rgb:F8F8FF,rgb:4078C0\nface global MatchingChar rgb:F8F8FF,rgb:4078C0+b\nface global Search default,default+u\nface global BufferPadding rgb:A0A0A0,rgb:F8F8FF\nface global Whitespace rgb:A0A0A0+f\n"
  },
  {
    "path": "colors/greyscale.kak",
    "content": "# Greyscale: monochromatic grey-based light colorscheme\n\nevaluate-commands %sh{\n    grey_light_5=\"rgb:fafafa\"\n    grey_light_4=\"rgb:f5f5f5\"\n    grey_light_3=\"rgb:eeeeee\"\n    grey_light_2=\"rgb:e0e0e0\"\n    grey_light_1=\"rgb:bdbdbd\"\n    grey=\"rgb:9e9e9e\"\n    grey_dark_1=\"rgb:757575\"\n    grey_dark_2=\"rgb:616161\"\n    grey_dark_3=\"rgb:424242\"\n    grey_dark_4=\"rgb:212121\"\n\n    cat <<EOF\n\n    # For Code\n    set-face global keyword ${grey_dark_2}\n    set-face global attribute ${grey_dark_2}\n    set-face global type ${grey_dark_2}\n    set-face global string ${grey_dark_1}\n    set-face global value ${grey_dark_1}+b\n    set-face global meta ${grey_dark_1}\n    set-face global builtin ${grey}+b\n    set-face global module ${grey_dark_1}\n    set-face global comment ${grey}+i\n    set-face global documentation comment\n    set-face global function Default\n    set-face global operator Default\n    set-face global variable Default\n\n    # For markup\n    set-face global title ${grey_dark_2}+b\n    set-face global header ${grey_dark_2}\n    set-face global block ${grey_dark_1}\n    set-face global mono ${grey_dark_1}\n    set-face global link ${grey}+u\n    set-face global list Default\n    set-face global bullet +b\n\n    # Built-in faces\n    set-face global Default ${grey},${grey_light_2}\n    set-face global PrimarySelection ${grey_light_3},${grey_dark_4}+fg\n    set-face global SecondarySelection ${grey_light_2},${grey_dark_3}+fg\n    set-face global PrimaryCursor ${grey_light_3},${grey_dark_1}+fg\n    set-face global SecondaryCursor ${grey_light_3},${grey}+fg\n    set-face global PrimaryCursorEol ${grey_light_1},${grey_dark_2}+fg\n    set-face global SecondaryCursorEol ${grey_light_2},${grey_dark_1}+fg\n\n    set-face global StatusLine ${grey_dark_3},${grey_light_1}\n    set-face global StatusLineMode ${grey_light_2},${grey_dark_3}\n    set-face global StatusLineInfo ${grey_light_2},${grey_dark_2}\n    set-face global StatusLineValue ${grey_light_3},${grey_dark_2}+b\n    set-face global StatusCursor ${grey_light_3},${grey}\n    set-face global Prompt ${grey_light_2},${grey_dark_3}\n    set-face global MenuForeground ${grey_light_4},${grey}\n    set-face global MenuBackground ${grey_dark_2},${grey_light_3}\n    set-face global MenuInfo ${grey}+i\n\n    set-face global LineNumbers ${grey_light_5},${grey_dark_1}\n    set-face global LineNumbersWrapped ${grey_light_2},${grey_dark_2}+i\n    set-face global LineNumberCursor ${grey_light_2},${grey_dark_3}+b\n    set-face global MatchingChar ${grey_dark_4},${grey_light_1}\n    set-face global Whitespace ${grey_light_1}+f\n    set-face global WrapMarker ${grey_light_1}+f\n\n    set-face global Information ${grey_light_2},${grey_dark_2}\n    set-face global Error ${grey_light_2},${grey_dark_3}\n    set-face global DiagnosticError ${grey_dark_3}\n    set-face global DiagnosticWarning ${grey_dark_2}\n    set-face global BufferPadding ${grey_light_1}\n\nEOF\n}\n"
  },
  {
    "path": "colors/gruvbox-dark.kak",
    "content": "# gruvbox-dark theme\n\nevaluate-commands %sh{\n    gray=\"rgb:928374\"\n    red=\"rgb:fb4934\"\n    green=\"rgb:b8bb26\"\n    yellow=\"rgb:fabd2f\"\n    blue=\"rgb:83a598\"\n    purple=\"rgb:d3869b\"\n    aqua=\"rgb:8ec07c\"\n    orange=\"rgb:fe8019\"\n\n    bg=\"rgb:282828\"\n    bg_alpha=\"rgba:282828a0\"\n    bg1=\"rgb:3c3836\"\n    bg2=\"rgb:504945\"\n    bg3=\"rgb:665c54\"\n    bg4=\"rgb:7c6f64\"\n\n    fg=\"rgb:ebdbb2\"\n    fg_alpha=\"rgba:ebdbb2a0\"\n    fg0=\"rgb:fbf1c7\"\n    fg2=\"rgb:d5c4a1\"\n    fg3=\"rgb:bdae93\"\n    fg4=\"rgb:a89984\"\n\n    echo \"\n        # Code highlighting\n        face global value         ${purple}\n        face global type          ${yellow}\n        face global variable      ${blue}\n        face global module        ${green}\n        face global function      ${fg}\n        face global string        ${green}\n        face global keyword       ${red}\n        face global operator      ${fg}\n        face global attribute     ${orange}\n        face global comment       ${gray}+i\n        face global documentation comment\n        face global meta          ${aqua}\n        face global builtin       ${fg}+b\n\n        # Markdown highlighting\n        face global title     ${green}+b\n        face global header    ${orange}\n        face global mono      ${fg4}\n        face global block     ${aqua}\n        face global link      ${blue}+u\n        face global bullet    ${yellow}\n        face global list      ${fg}\n\n        face global Default            ${fg},${bg}\n        face global PrimarySelection   ${fg_alpha},${blue}+g\n        face global SecondarySelection ${bg_alpha},${blue}+g\n        face global PrimaryCursor      ${bg},${fg}+fg\n        face global SecondaryCursor    ${bg},${bg4}+fg\n        face global PrimaryCursorEol   ${bg},${fg4}+fg\n        face global SecondaryCursorEol ${bg},${bg2}+fg\n        face global LineNumbers        ${bg4}\n        face global LineNumberCursor   ${yellow},${bg1}\n        face global LineNumbersWrapped ${bg1}\n        face global MenuForeground     ${bg2},${blue}\n        face global MenuBackground     ${fg},${bg2}\n        face global MenuInfo           ${bg}\n        face global Information        ${bg},${fg}\n        face global Error              ${bg},${red}\n        face global DiagnosticError    ${red}\n        face global DiagnosticWarning  ${yellow}\n        face global StatusLine         ${fg},${bg}\n        face global StatusLineMode     ${yellow}+b\n        face global StatusLineInfo     ${purple}\n        face global StatusLineValue    ${red}\n        face global StatusCursor       ${bg},${fg}\n        face global Prompt             ${yellow}\n        face global MatchingChar       ${fg},${bg3}+b\n        face global BufferPadding      ${bg2},${bg}\n        face global Whitespace         ${bg2}+f\n    \"\n}\n"
  },
  {
    "path": "colors/gruvbox-light.kak",
    "content": "# gruvbox light theme\n\nevaluate-commands %sh{\n    gray=\"rgb:928374\"\n    red=\"rgb:9d0006\"\n    green=\"rgb:79740e\"\n    yellow=\"rgb:b57614\"\n    blue=\"rgb:876678\"\n    purple=\"rgb:8f3f71\"\n    aqua=\"rgb:427b58\"\n    orange=\"rgb:af3a03\"\n\n    bg=\"rgb:fbf1c7\"\n    bg_alpha=\"rgba:fbf1c7a0\"\n    bg1=\"rgb:ebdbb2\"\n    bg2=\"rgb:d5c4a1\"\n    bg3=\"rgb:bdae93\"\n    bg4=\"rgb:a89984\"\n\n    fg=\"rgb:3c3836\"\n    fg_alpha=\"rgba:3c3836a0\"\n    fg0=\"rgb:282828\"\n    fg2=\"rgb:504945\"\n    fg3=\"rgb:665c54\"\n    fg4=\"rgb:7c6f64\"\n\n    echo \"\n        # Code highlighting\n        face global value         ${purple}\n        face global type          ${yellow}\n        face global variable      ${blue}\n        face global module        ${green}\n        face global function      ${fg}\n        face global string        ${green}\n        face global keyword       ${red}\n        face global operator      ${fg}\n        face global attribute     ${orange}\n        face global comment       ${gray}+i\n        face global documentation comment\n        face global meta          ${aqua}\n        face global builtin       ${fg}+b\n\n        # Markdown highlighting\n        face global title     ${green}+b\n        face global header    ${orange}\n        face global mono      ${fg4}\n        face global block     ${aqua}\n        face global link      ${blue}+u\n        face global bullet    ${yellow}\n        face global list      ${fg}\n\n        face global Default            ${fg},${bg}\n        face global PrimarySelection   ${fg_alpha},${blue}+g\n        face global SecondarySelection ${bg_alpha},${blue}+g\n        face global PrimaryCursor      ${bg},${fg}+fg\n        face global SecondaryCursor    ${bg},${bg4}+fg\n        face global PrimaryCursorEol   ${bg},${fg4}+fg\n        face global SecondaryCursorEol ${bg},${bg2}+fg\n        face global LineNumbers        ${bg4}\n        face global LineNumberCursor   ${yellow},${bg1}\n        face global LineNumbersWrapped ${bg1}\n        face global MenuForeground     ${bg2},${blue}\n        face global MenuBackground     ${fg},${bg2}\n        face global MenuInfo           ${bg}\n        face global Information        ${bg},${fg}\n        face global Error              ${bg},${red}\n        face global DiagnosticError    ${red}\n        face global DiagnosticWarning  ${yellow}\n        face global StatusLine         ${fg},${bg}\n        face global StatusLineMode     ${yellow}+b\n        face global StatusLineInfo     ${purple}\n        face global StatusLineValue    ${red}\n        face global StatusCursor       ${bg},${fg}\n        face global Prompt             ${yellow}\n        face global MatchingChar       ${fg},${bg3}+b\n        face global BufferPadding      ${bg2},${bg}\n        face global Whitespace         ${bg2}+f\n    \"\n}\n"
  },
  {
    "path": "colors/kaleidoscope-dark.kak",
    "content": "# Kaleidoscope: colorblind-friendly dark colorscheme\n# https://personal.sron.nl/~pault/\n\nevaluate-commands %sh{\n    # NOTE: tone down black and white for aesthetics,\n    # ideally those should be pure #000 and #FFF\n    black=\"rgb:303030\"\n    white=\"rgb:FDFDFD\"\n\n    # Regular text\n    bright_blue=\"rgb:4477AA\"\n    bright_cyan=\"rgb:66CCEE\"\n    bright_green=\"rgb:228833\"\n    bright_yellow=\"rgb:CCBB44\"\n    bright_red=\"rgb:EE6677\"\n    bright_purple=\"rgb:AA3377\"\n    bright_grey=\"rgb:BBBBBB\"\n\n    # Emphasis\n    high_contrast_blue=\"rgb:004488\"\n    high_contrast_yellow=\"rgb:DDAA33\"\n    high_contrast_red=\"rgb:BB5566\"\n\n    # High contrast alternative text\n    vibrant_orange=\"rgb:EE7733\"\n    vibrant_blue=\"rgb:0077BB\"\n    vibrant_cyan=\"rgb:33BBEE\"\n    vibrant_magenta=\"rgb:EE3377\"\n    vibrant_red=\"rgb:CC3311\"\n    vibrant_teal=\"rgb:009988\"\n    vibrant_grey=\"rgb:BBBBBB\"\n\n    # Darker text with no red\n    muted_rose=\"rgb:CC6677\"\n    muted_indigo=\"rgb:332288\"\n    muted_sand=\"rgb:DDCC77\"\n    muted_green=\"rgb:117733\"\n    muted_cyan=\"rgb:88CCEE\"\n    muted_wine=\"rgb:882255\"\n    muted_teal=\"rgb:44AA99\"\n    muted_olive=\"rgb:999933\"\n    muted_purple=\"rgb:AA4499\"\n    muted_pale_grey=\"rgb:DDDDDD\"\n\n    # Low contrast background colors\n    light_blue=\"rgb:77AADD\"\n    light_orange=\"rgb:EE8866\"\n    light_yellow=\"rgb:EEDD88\"\n    light_pink=\"rgb:FFAABB\"\n    light_cyan=\"rgb:99DDFF\"\n    light_mint=\"rgb:44BB99\"\n    light_pear=\"rgb:BBCC33\"\n    light_olive=\"rgb:AAAA00\"\n    light_grey=\"rgb:DDDDDD\"\n\n    # Pale background colors, black foreground\n    pale_blue=\"rgb:BBCCEE\"\n    pale_cyan=\"rgb:CCEEFF\"\n    pale_green=\"rgb:CCDDAA\"\n    pale_yellow=\"rgb:EEEEBB\"\n    pale_red=\"rgb:FFCCCC\"\n    pale_grey=\"rgb:DDDDDD\"\n\n    # Dark background colors, white foreground\n    dark_blue=\"rgb:222255\"\n    dark_cyan=\"rgb:225555\"\n    dark_green=\"rgb:225522\"\n    dark_yellow=\"rgb:666633\"\n    dark_red=\"rgb:663333\"\n    dark_grey=\"rgb:555555\"\n\n    # NOTE: Do not use any color that hasn't been defined above (no hardcoding)\n    cat <<EOF\n\n    # For Code\n    set-face global keyword ${vibrant_blue}\n    set-face global attribute ${muted_purple}\n    set-face global type ${vibrant_blue}\n    set-face global string ${muted_rose}\n    set-face global value ${light_pink}\n    set-face global meta ${light_olive}\n    set-face global builtin ${vibrant_blue}+b\n    set-face global module ${vibrant_orange}\n    set-face global comment ${bright_green}+i\n    set-face global documentation comment\n    set-face global function Default\n    set-face global operator Default\n    set-face global variable Default\n\n    # For markup\n    set-face global title ${vibrant_blue}+b\n    set-face global header ${muted_cyan}\n    set-face global block ${vibrant_magenta}\n    set-face global mono ${vibrant_magenta}\n    set-face global link ${bright_cyan}+u\n    set-face global list Default\n    set-face global bullet +b\n\n    # Built-in faces\n    set-face global Default ${white},${black}\n    set-face global PrimarySelection ${black},${pale_blue}+fg\n    set-face global SecondarySelection ${black},${pale_cyan}+fg\n    set-face global PrimaryCursor ${white},${high_contrast_blue}+fg\n    set-face global SecondaryCursor ${white},${dark_cyan}+fg\n    set-face global PrimaryCursorEol ${black},${vibrant_grey}+fg\n    set-face global SecondaryCursorEol ${black},${pale_grey}+fg\n\n    set-face global StatusLine ${black},${vibrant_grey}\n    set-face global StatusLineMode ${black},${light_blue}\n    set-face global StatusLineInfo ${black},${light_yellow}\n    set-face global StatusLineValue ${high_contrast_red},${light_yellow}+b\n    set-face global StatusCursor ${black},${light_orange}\n    set-face global Prompt ${black},${light_yellow}\n    set-face global MenuForeground ${black},${light_yellow}\n    set-face global MenuBackground ${black},${pale_grey}\n    set-face global MenuInfo ${vibrant_blue}+i\n\n    set-face global LineNumbers ${white},${dark_grey}\n    set-face global LineNumbersWrapped ${black},${vibrant_grey}+i\n    set-face global LineNumberCursor ${black},${pale_grey}+b\n    set-face global MatchingChar ${black},${vibrant_grey}\n    set-face global Whitespace ${dark_grey}+f\n    set-face global WrapMarker ${dark_grey}+f\n\n    set-face global Information ${black},${light_yellow}\n    set-face global Error ${white},${vibrant_red}\n    set-face global DiagnosticError ${high_contrast_red}\n    set-face global DiagnosticWarning ${high_contrast_yellow}\n    set-face global BufferPadding ${dark_grey}\n\nEOF\n}\n"
  },
  {
    "path": "colors/kaleidoscope-light.kak",
    "content": "# Kaleidoscope: colorblind-friendly light colorscheme\n# https://personal.sron.nl/~pault/\n\nevaluate-commands %sh{\n    # NOTE: tone down black and white for aesthetics,\n    # ideally those should be pure #000 and #FFF\n    black=\"rgb:1C1C1C\"\n    white=\"rgb:FDFDFD\"\n\n    # Regular text\n    bright_blue=\"rgb:4477AA\"\n    bright_cyan=\"rgb:66CCEE\"\n    bright_green=\"rgb:228833\"\n    bright_yellow=\"rgb:CCBB44\"\n    bright_red=\"rgb:EE6677\"\n    bright_purple=\"rgb:AA3377\"\n    bright_grey=\"rgb:BBBBBB\"\n\n    # Emphasis\n    high_contrast_blue=\"rgb:004488\"\n    high_contrast_yellow=\"rgb:DDAA33\"\n    high_contrast_red=\"rgb:BB5566\"\n\n    # High contrast alternative text\n    vibrant_orange=\"rgb:EE7733\"\n    vibrant_blue=\"rgb:0077BB\"\n    vibrant_cyan=\"rgb:33BBEE\"\n    vibrant_magenta=\"rgb:EE3377\"\n    vibrant_red=\"rgb:CC3311\"\n    vibrant_teal=\"rgb:009988\"\n    vibrant_grey=\"rgb:BBBBBB\"\n\n    # Darker text with no red\n    muted_rose=\"rgb:CC6677\"\n    muted_indigo=\"rgb:332288\"\n    muted_sand=\"rgb:DDCC77\"\n    muted_green=\"rgb:117733\"\n    muted_cyan=\"rgb:88CCEE\"\n    muted_wine=\"rgb:882255\"\n    muted_teal=\"rgb:44AA99\"\n    muted_olive=\"rgb:999933\"\n    muted_purple=\"rgb:AA4499\"\n    muted_pale_grey=\"rgb:DDDDDD\"\n\n    # Low contrast background colors\n    light_blue=\"rgb:77AADD\"\n    light_orange=\"rgb:EE8866\"\n    light_yellow=\"rgb:EEDD88\"\n    light_pink=\"rgb:FFAABB\"\n    light_cyan=\"rgb:99DDFF\"\n    light_mint=\"rgb:44BB99\"\n    light_pear=\"rgb:BBCC33\"\n    light_olive=\"rgb:AAAA00\"\n    light_grey=\"rgb:DDDDDD\"\n\n    # Pale background colors, black foreground\n    pale_blue=\"rgb:BBCCEE\"\n    pale_cyan=\"rgb:CCEEFF\"\n    pale_green=\"rgb:CCDDAA\"\n    pale_yellow=\"rgb:EEEEBB\"\n    pale_red=\"rgb:FFCCCC\"\n    pale_grey=\"rgb:DDDDDD\"\n\n    # Dark background colors, white foreground\n    dark_blue=\"rgb:222255\"\n    dark_cyan=\"rgb:225555\"\n    dark_green=\"rgb:225522\"\n    dark_yellow=\"rgb:666633\"\n    dark_red=\"rgb:663333\"\n    dark_grey=\"rgb:555555\"\n\n    # NOTE: Do not use any color that hasn't been defined above (no hardcoding)\n    cat <<EOF\n\n    # For Code\n    set-face global keyword ${muted_indigo}\n    set-face global attribute ${muted_purple}\n    set-face global type ${vibrant_blue}\n    set-face global string ${muted_wine}\n    set-face global value ${muted_rose}\n    set-face global meta ${muted_olive}\n    set-face global builtin ${muted_indigo}+b\n    set-face global module ${vibrant_orange}\n    set-face global comment ${muted_green}+i\n    set-face global documentation comment\n    set-face global function Default\n    set-face global operator Default\n    set-face global variable Default\n\n    # For markup\n    set-face global title ${muted_indigo}+b\n    set-face global header ${high_contrast_blue}\n    set-face global block ${vibrant_magenta}\n    set-face global mono ${vibrant_red}\n    set-face global link ${vibrant_blue}+u\n    set-face global list Default\n    set-face global bullet +b\n\n    # Built-in faces\n    set-face global Default ${black},${white}\n    set-face global PrimarySelection ${black},${pale_blue}+fg\n    set-face global SecondarySelection ${black},${pale_cyan}+fg\n    set-face global PrimaryCursor ${white},${dark_blue}+fg\n    set-face global SecondaryCursor ${white},${dark_cyan}+fg\n    set-face global PrimaryCursorEol ${white},${dark_grey}+fg\n    set-face global SecondaryCursorEol ${white},${vibrant_grey}+fg\n\n    set-face global StatusLine ${white},${dark_grey}\n    set-face global StatusLineMode ${black},${pale_blue}\n    set-face global StatusLineInfo ${black},${muted_sand}\n    set-face global StatusLineValue ${vibrant_orange},${muted_sand}+b\n    set-face global StatusCursor ${black},${high_contrast_yellow}\n    set-face global Prompt ${black},${muted_sand}\n    set-face global MenuForeground ${black},${muted_sand}\n    set-face global MenuBackground ${black},${pale_grey}\n    set-face global MenuInfo ${high_contrast_blue}+i\n\n    set-face global LineNumbers ${black},${pale_grey}\n    set-face global LineNumbersWrapped ${black},${vibrant_grey}+i\n    set-face global LineNumberCursor ${white},${dark_grey}+b\n    set-face global MatchingChar ${white},${dark_grey}\n    set-face global Whitespace ${vibrant_grey}+f\n    set-face global WrapMarker ${vibrant_grey}+f\n\n    set-face global Information ${black},${muted_sand}\n    set-face global Error ${white},${vibrant_red}\n    set-face global DiagnosticError ${high_contrast_red}\n    set-face global DiagnosticWarning ${high_contrast_yellow}\n    set-face global BufferPadding ${vibrant_grey}\n\nEOF\n}\n"
  },
  {
    "path": "colors/lucius.kak",
    "content": "# lucius theme\n\nevaluate-commands %sh{\n    # first we define the lucius colors as named colors\n    lucius_darker_grey=\"rgb:303030\"\n    lucius_dark_grey=\"rgb:444444\"\n    lucius_grey=\"rgb:808080\"\n    lucius_light_grey=\"rgb:b2b2b2\"\n    lucius_lighter_grey=\"rgb:d7d7d7\"\n\n    lucius_dark_red=\"rgb:870000\"\n    lucius_light_red=\"rgb:ff8787\"\n    lucius_orange=\"rgb:d78700\"\n    lucius_purple=\"rgb:d7afd7\"\n\n    lucius_dark_green=\"rgb:5f875f\"\n    lucius_bright_green=\"rgb:87af00\"\n    lucius_green=\"rgb:afd787\"\n    lucius_light_green=\"rgb:d7d7af\"\n\n    lucius_dark_blue=\"rgb:005f87\"\n    lucius_blue=\"rgb:87afd7\"\n    lucius_light_blue=\"rgb:87d7ff\"\n\n    echo \"\n        # then we map them to code\n        face global value ${lucius_light_green}\n        face global type ${lucius_blue}\n        face global variable ${lucius_green}\n        face global module ${lucius_green}\n        face global function ${lucius_light_blue}\n        face global string ${lucius_light_green}\n        face global keyword ${lucius_light_blue}\n        face global operator ${lucius_green}\n        face global attribute ${lucius_light_blue}\n        face global comment ${lucius_grey}\n        face global documentation comment\n        face global meta ${lucius_purple}\n        face global builtin default+b\n\n        # and markup\n        face global title ${lucius_light_blue}\n        face global header ${lucius_light_green}\n        face global mono ${lucius_light_green}\n        face global block ${lucius_light_blue}\n        face global link ${lucius_light_green}\n        face global bullet ${lucius_green}\n        face global list ${lucius_blue}\n\n        # and built in faces\n        face global Default ${lucius_lighter_grey},${lucius_darker_grey}\n        face global PrimarySelection ${lucius_darker_grey},${lucius_orange}+fg\n        face global SecondarySelection  ${lucius_lighter_grey},${lucius_dark_blue}+fg\n        face global PrimaryCursor ${lucius_darker_grey},${lucius_lighter_grey}+fg\n        face global SecondaryCursor ${lucius_darker_grey},${lucius_lighter_grey}+fg\n        face global PrimaryCursorEol ${lucius_darker_grey},${lucius_dark_green}+fg\n        face global SecondaryCursorEol ${lucius_darker_grey},${lucius_dark_green}+fg\n        face global LineNumbers ${lucius_grey},${lucius_dark_grey}\n        face global LineNumberCursor ${lucius_grey},${lucius_dark_grey}+b\n        face global MenuForeground ${lucius_blue},${lucius_dark_blue}\n        face global MenuBackground ${lucius_darker_grey},${lucius_light_grey}\n        face global MenuInfo ${lucius_grey}\n        face global Information ${lucius_lighter_grey},${lucius_dark_green}\n        face global Error ${lucius_light_red},${lucius_dark_red}\n        face global DiagnosticError ${lucius_light_red}\n        face global DiagnosticWarning ${lucius_purple}\n        face global StatusLine ${lucius_lighter_grey},${lucius_dark_grey}\n        face global StatusLineMode ${lucius_lighter_grey},${lucius_dark_green}+b\n        face global StatusLineInfo ${lucius_dark_grey},${lucius_lighter_grey}\n        face global StatusLineValue ${lucius_lighter_grey}\n        face global StatusCursor default,${lucius_blue}\n        face global Prompt ${lucius_lighter_grey}\n        face global MatchingChar ${lucius_lighter_grey},${lucius_bright_green}\n        face global BufferPadding ${lucius_green},${lucius_darker_grey}\n        face global Whitespace ${lucius_grey}+f\n    \"\n}\n"
  },
  {
    "path": "colors/palenight.kak",
    "content": "# palenight theme\n\n# This was ported from https://github.com/drewtempelmeyer/palenight.vim\n\nevaluate-commands %sh{\n    red=rgb:ff5370\n    light_red=rgb:ff869a\n    dark_red=rgb:be5046\n    green=rgb:c3e88d\n    yellow=rgb:ffcb6b\n    dark_yellow=rgb:f78c6c\n    blue=rgb:82b1ff\n    purple=rgb:c792ea\n    cyan=rgb:89ddff\n    white=rgb:bfc7d5\n    black=rgb:292d3e\n    comment_grey=rgb:697098\n    gutter_fg_grey=rgb:4b5263\n    cursor_grey=rgb:2c323c\n    visual_grey=rgb:3e4452\n    menu_grey=rgb:697098\n    special_grey=rgb:3b4048\n    vertsplit=rgb:181a1f\n    visual_black=default\n\n    printf \"%s\\n\" \"\n    # Code\n    face global value         $dark_yellow\n    face global type          $yellow\n    face global function      $blue\n    face global variable      $blue\n    face global identifier    $blue\n    face global string        $green\n    face global error         rgb:c3bf9f+b\n    face global keyword       $purple\n    face global operator      $cyan\n    face global attribute     rgb:eedc82\n    face global comment       $comment_grey+i\n    face global documentation comment\n\n    # #include <...>\n    face global meta       $yellow\n\n    # Markup\n    face global title  $blue\n    face global header $cyan\n    face global mono   $green\n    face global block  $purple\n    face global link   $cyan\n    face global bullet $cyan\n    face global list   $yellow\n\n    # Builtin\n    face global Default            $white,$black\n\n    face global PrimarySelection   $black,$white+bfg\n    face global SecondarySelection $black,$white+fg\n\n    face global PrimaryCursor      white,$purple+bfg\n    face global SecondaryCursor    $black,$purple+fg\n\n    face global PrimaryCursorEol   $black,$green+fg\n    face global SecondaryCursorEol $black,$green+fg\n\n    face global LineNumbers        $gutter_fg_grey\n    face global LineNumberCursor   $yellow,default+b\n\n    # Bottom menu:\n    # text + background\n    face global MenuBackground     $black,$white\n    face global MenuForeground     $black,$purple\n\n    # completion menu info\n    face global MenuInfo           $menu_grey,default+i\n\n    # assistant, [+]\n    face global Information        $white,$visual_grey\n\n    face global Error              $white,$red\n    face global DiagnosticError    $red\n    face global DiagnosticWarning  $yellow\n    face global StatusLine         $white,$black\n\n    # Status line\n    face global StatusLineMode     $black,$purple      # insert, prompt, enter key ...\n    face global StatusLineInfo     $white,$visual_grey # 1 sel\n    face global StatusLineValue    $visual_grey,$green # param=value, reg=value. ex: \\\"ey\n    face global StatusCursor       white,$purple+bg\n\n    face global Prompt             $purple,$black # :\n    face global MatchingChar       $red+b         # (), {}\n    face global BufferPadding      $gutter_fg_grey,$black   # EOF tildas (~)\n\n    # Whitespace characters\n    face global Whitespace         $gutter_fg_grey,$black+fg\n    \"\n}\n"
  },
  {
    "path": "colors/plain.kak",
    "content": "\n# Kakoune simple colors, mostly default\n\n# For default\nface global value default\nface global type default\nface global identifier default\nface global string blue\nface global keyword default\nface global operator default\nface global attribute default\nface global comment blue\nface global documentation comment\nface global meta default\nface global builtin default\n\n\n# For default\nface global title default\nface global header default\nface global mono default\nface global block default\nface global link blue\nface global bullet default\nface global list default\n\n# builtin default\nface global Default default,default\nface global PrimarySelection white,blue\nface global SecondarySelection black,blue\nface global PrimaryCursor black,white\nface global SecondaryCursor white,blue\nface global PrimaryCursorEol white,blue\nface global SecondaryCursorEol default\nface global LineNumbers default\nface global LineNumberCursor default\nface global MenuForeground black,blue\nface global MenuBackground blue,black\nface global MenuInfo default\nface global Information blue,black\nface global Error default\nface global DiagnosticError default\nface global DiagnosticWarning default\nface global StatusLine default\nface global StatusLineMode default\nface global StatusLineInfo default\nface global StatusLineValue default\nface global StatusCursor default+r\nface global Prompt default\nface global MatchingChar default\nface global BufferPadding default\n"
  },
  {
    "path": "colors/red-phoenix.kak",
    "content": "##\n## Red Phoenix (dkeg) - adapted by boj\n##\n\nevaluate-commands %sh{\n    black=\"rgb:000000\"\n    blue=\"rgb:81a2be\"\n\n    orange1=\"rgb:F2361E\"\n    orange2=\"rgb:ED4B19\"\n    orange3=\"rgb:FA390F\"\n    light_orange1=\"rgb:DF9767\"\n    white1=\"rgb:EDEDED\"\n    white2=\"rgb:E1E1E1\"\n    gray1=\"rgb:6F6F6F\"\n    gray2=\"rgb:D1D1D1\"\n    gray3=\"rgb:2D2D2D\"\n    gray4=\"rgb:909090\"\n    tan1=\"rgb:D2C3AD\"\n    tan2=\"rgb:AAA998\"\n    tan3=\"rgb:DF9767\"\n    yellow1=\"rgb:AAA998\"\n    purple1=\"rgb:4C3A3D\"\n\n    foreground=${white1}\n    background=${black}\n    selection=${purple1}\n    window=${gray3}\n    text=${white2}\n    text_light=${white1}\n    line=${tan1}\n    comment=${gray1}\n\n    ## code\n    echo \"\n        face global value ${orange2}\n        face global type ${gray2}\n        face global variable ${orange1}\n        face global module ${gray2}\n        face global function ${yellow1}\n        face global string ${tan2}\n        face global keyword ${light_orange1}\n        face global operator ${yellow1}\n        face global attribute ${tan1}\n        face global comment ${gray1}\n        face global documentation comment\n        face global meta ${gray2}\n        face global builtin ${tan1}\n    \"\n\n    ## markup\n    echo \"\n        face global title blue\n        face global header ${orange1}\n        face global mono ${yellow1}\n        face global block ${tan1}\n        face global link blue\n        face global bullet ${gray1}\n        face global list ${gray1}\n    \"\n\n    ## builtin\n    echo \"\n        face global Default ${text},${background}\n        face global PrimarySelection default,${selection}+fg\n        face global SecondarySelection default,${selection}+fg\n        face global PrimaryCursor black,${tan1}+fg\n        face global SecondaryCursor black,${tan2}+fg\n        face global PrimaryCursorEol black,${orange1}+fg\n        face global SecondaryCursorEol black,${orange2}+fg\n        face global LineNumbers ${text_light},${background}\n        face global LineNumberCursor ${text},${gray1}+b\n        face global MenuForeground ${text_light},blue\n        face global MenuBackground ${orange1},${window}\n        face global MenuInfo ${gray1}\n        face global Information white,${window}\n        face global Error white,${gray1}\n        face global DiagnosticError ${orange1}\n        face global DiagnosticWarning ${orange2}\n        face global StatusLine ${text},${window}\n        face global StatusLineMode ${yellow1}+b\n        face global StatusLineInfo ${orange2}\n        face global StatusLineValue ${orange2}\n        face global StatusCursor ${window},${orange2}\n        face global Prompt ${background},${orange2}\n        face global MatchingChar ${orange3},${background}+b\n        face global BufferPadding ${orange2},${background}\n        face global Whitespace default+f\n    \"\n}\n"
  },
  {
    "path": "colors/reeder.kak",
    "content": "##\n## reeder theme\n## a light theme inspired after https://github.com/hyspace/st2-reeder-theme\n##\n\nevaluate-commands %sh{\n    white=\"rgb:f9f8f6\"\n    white_light=\"rgb:f6f5f0\"\n    black=\"rgb:383838\"\n    black_light=\"rgb:635240\"\n    grey_dark=\"rgb:c6b0a4\"\n    grey_light=\"rgb:e8e8e8\"\n    brown_dark=\"rgb:af4609\"\n    brown_light=\"rgb:baa188\"\n    brown_lighter=\"rgb:f0e7df\"\n    orange=\"rgb:fc7302\"\n    orange_light=\"rgb:f88e3b\"\n    green=\"rgb:438047\"\n    green_light=\"rgb:7ba84d\"\n    red=\"rgb:f03c3c\"\n\n    # Base color definitions\n    echo \"\n        # then we map them to code\n        face global value         ${orange_light}+b\n        face global type          ${orange}\n        face global variable      default\n        face global module        ${green}\n        face global function      default\n        face global string        ${green}\n        face global keyword       ${brown_dark}\n        face global operator      default\n        face global attribute     ${green}\n        face global comment       ${brown_light}\n        face global documentation comment\n        face global meta          ${brown_dark}\n        face global builtin       default+b\n\n        # and markup\n        face global title      ${orange}+b\n        face global header     ${orange}+b\n        face global mono       ${green_light}\n        face global block      ${green}\n        face global link       ${orange}\n        face global bullet     ${brown_dark}\n        face global list       ${black}\n\n        # and built in faces\n        face global Default            ${black_light},${white}\n        face global PrimarySelection   ${black},${brown_lighter}+fg\n        face global SecondarySelection ${black_light},${grey_light}+fg\n        face global PrimaryCursor      ${black},${grey_dark}+fg\n        face global SecondaryCursor    ${black},${grey_dark}+fg\n        face global PrimaryCursorEol   ${black},${brown_dark}+fg\n        face global SecondaryCursorEol ${black},${brown_dark}+fg\n        face global LineNumbers        ${grey_dark},${white}\n        face global LineNumberCursor   ${grey_dark},${brown_lighter}\n        face global MenuForeground     ${orange},${brown_lighter}\n        face global MenuBackground     ${black_light},${brown_lighter}\n        face global MenuInfo           default,${black}\n        face global Information        ${black_light},${brown_lighter}\n        face global Error              default,${red}\n        face global DiagnosticError    ${red}\n        face global DiagnosticWarning  ${orange}\n        face global StatusLine         ${black},${grey_light}\n        face global StatusLineMode     ${orange}\n        face global StatusLineInfo     ${black}+b\n        face global StatusLineValue    ${green_light}\n        face global StatusCursor       ${orange},${white_light}\n        face global Prompt             ${black_light}\n        face global MatchingChar       default+b\n        face global BufferPadding      ${grey_dark},${white}\n        face global Whitespace         ${grey_dark}+f\n    \"\n}\n"
  },
  {
    "path": "colors/solarized-dark-termcolors.kak",
    "content": "# Solarized Dark (with termcolors)\n# Useful if you've set up your terminal with the exact Solarized colors\n\n# code\nface global value              cyan\nface global type               yellow\nface global variable           blue\nface global module             cyan\nface global function           blue\nface global string             cyan\nface global keyword            green\nface global operator           green\nface global attribute          bright-magenta\nface global comment            bright-green\nface global documentation      comment\nface global meta               bright-red\nface global builtin            default+b\n\n# markup\nface global title              blue+b\nface global header             blue\nface global mono               bright-cyan\nface global block              cyan\nface global link               bright-cyan\nface global bullet             yellow\nface global list               green\n\n# builtin\nface global Default            bright-blue\nface global PrimarySelection   bright-black,blue+fg\nface global SecondarySelection bright-green,bright-cyan+fg\nface global PrimaryCursor      bright-black,bright-blue+fg\nface global SecondaryCursor    bright-black,bright-green+fg\nface global PrimaryCursorEol   bright-black,white+fg\nface global SecondaryCursorEol bright-black,bright-white+fg\nface global LineNumbers        bright-green,black\nface global LineNumberCursor   bright-cyan,black\nface global LineNumbersWrapped black,black\nface global MenuForeground     bright-black,yellow\nface global MenuBackground     bright-cyan,black\nface global MenuInfo           bright-green\nface global Information        black,bright-cyan\nface global Error              red,default+b\nface global DiagnosticError    red\nface global DiagnosticWarning  yellow\nface global StatusLine         bright-cyan,black+b\nface global StatusLineMode     bright-red\nface global StatusLineInfo     cyan\nface global StatusLineValue    green\nface global StatusCursor       bright-yellow,bright-white\nface global Prompt             yellow+b\nface global MatchingChar       red,bright-green+b\nface global BufferPadding      bright-green\nface global Whitespace         blue+f\n"
  },
  {
    "path": "colors/solarized-dark.kak",
    "content": "# Solarized Dark\n\nevaluate-commands %sh{\n        base03='rgb:002b36'\n        base02='rgb:073642'\n        base01='rgb:586e75'\n        base00='rgb:657b83'\n        base0='rgb:839496'\n        base1='rgb:93a1a1'\n        base2='rgb:eee8d5'\n        base3='rgb:fdf6e3'\n        yellow='rgb:b58900'\n        orange='rgb:cb4b16'\n        red='rgb:dc322f'\n        magenta='rgb:d33682'\n        violet='rgb:6c71c4'\n        blue='rgb:268bd2'\n        cyan='rgb:2aa198'\n        green='rgb:859900'\n\n   echo \"\n        # code\n        face global value              ${cyan}\n        face global type               ${yellow}\n        face global variable           ${blue}\n        face global module             ${cyan}\n        face global function           ${blue}\n        face global string             ${cyan}\n        face global keyword            ${green}\n        face global operator           ${green}\n        face global attribute          ${violet}\n        face global comment            ${base01}\n        face global documentation      comment\n        face global meta               ${orange}\n        face global builtin            default+b\n\n        # markup\n        face global title              ${blue}+b\n        face global header             ${blue}\n        face global mono               ${base1}\n        face global block              ${cyan}\n        face global link               ${base1}\n        face global bullet             ${yellow}\n        face global list               ${green}\n\n        # builtin\n        face global Default            ${base0},${base03}\n        face global PrimarySelection   ${base03},${blue}+fg\n        face global SecondarySelection ${base01},${base1}+fg\n        face global PrimaryCursor      ${base03},${base0}+fg\n        face global SecondaryCursor    ${base03},${base01}+fg\n        face global PrimaryCursorEol   ${base03},${base2}+fg\n        face global SecondaryCursorEol ${base03},${base3}+fg\n        face global LineNumbers        ${base01},${base02}\n        face global LineNumberCursor   ${base1},${base02}\n        face global LineNumbersWrapped ${base02},${base02}\n        face global MenuForeground     ${base03},${yellow}\n        face global MenuBackground     ${base1},${base02}\n        face global MenuInfo           ${base01}\n        face global Information        ${base02},${base1}\n        face global Error              ${red},default+b\n        face global DiagnosticError    ${red}\n        face global DiagnosticWarning  ${yellow}\n        face global StatusLine         ${base1},${base02}+b\n        face global StatusLineMode     ${orange}\n        face global StatusLineInfo     ${cyan}\n        face global StatusLineValue    ${green}\n        face global StatusCursor       ${base00},${base3}\n        face global Prompt             ${yellow}+b\n        face global MatchingChar       ${red},${base01}+b\n        face global BufferPadding      ${base01},${base03}\n        face global Whitespace         ${base01}+f\n    \"\n}\n"
  },
  {
    "path": "colors/solarized-light-termcolors.kak",
    "content": "# Solarized Light (with termcolors)\n# Useful if you've set up your terminal with the exact Solarized colors\n\n# code\nface global value              cyan\nface global type               red\nface global variable           blue\nface global module             cyan\nface global function           blue\nface global string             cyan\nface global keyword            green\nface global operator           yellow\nface global attribute          bright-magenta\nface global comment            bright-cyan\nface global documentation      comment\nface global meta               bright-red\nface global builtin            default+b\n\n# markup\nface global title              blue+b\nface global header             blue\nface global mono               bright-cyan\nface global block              cyan\nface global link               bright-green\nface global bullet             yellow\nface global list               green\n\n# builtin\nface global Default            bright-yellow\nface global PrimarySelection   bright-white,blue+fg\nface global SecondarySelection bright-cyan,bright-green+fg\nface global PrimaryCursor      bright-white,bright-yellow+fg\nface global SecondaryCursor    bright-white,bright-cyan+fg\nface global PrimaryCursorEol   bright-white,yellow+fg\nface global SecondaryCursorEol bright-white,bright-red+fg\nface global LineNumbers        bright-cyan,white\nface global LineNumberCursor   bright-green,white\nface global LineNumbersWrapped white,white\nface global MenuForeground     bright-white,yellow\nface global MenuBackground     bright-green,white\nface global MenuInfo           bright-cyan\nface global Information        white,bright-cyan\nface global Error              red,default+b\nface global DiagnosticError    red\nface global DiagnosticWarning  yellow\nface global StatusLine         bright-green,white+b\nface global StatusLineMode     bright-red\nface global StatusLineInfo     cyan\nface global StatusLineValue    green\nface global StatusCursor       bright-blue,bright-black\nface global Prompt             yellow+b\nface global MatchingChar       red,white+b\nface global BufferPadding      bright-cyan\nface global Whitespace         yellow+f\n"
  },
  {
    "path": "colors/solarized-light.kak",
    "content": "# Solarized Light\n\nevaluate-commands %sh{\n\tbase03='rgb:002b36'\n\tbase02='rgb:073642'\n\tbase01='rgb:586e75'\n\tbase00='rgb:657b83'\n\tbase0='rgb:839496'\n\tbase1='rgb:93a1a1'\n\tbase2='rgb:eee8d5'\n\tbase3='rgb:fdf6e3'\n\tyellow='rgb:b58900'\n\torange='rgb:cb4b16'\n\tred='rgb:dc322f'\n\tmagenta='rgb:d33682'\n\tviolet='rgb:6c71c4'\n\tblue='rgb:268bd2'\n\tcyan='rgb:2aa198'\n\tgreen='rgb:859900'\n\n    echo \"\n        # code\n        face global value              ${cyan}\n        face global type               ${red}\n        face global variable           ${blue}\n        face global module             ${cyan}\n        face global function           ${blue}\n        face global string             ${cyan}\n        face global keyword            ${green}\n        face global operator           ${yellow}\n        face global attribute          ${violet}\n        face global comment            ${base1}\n        face global documentation      comment\n        face global meta               ${orange}\n        face global builtin            default+b\n\n        # markup\n        face global title              ${blue}+b\n        face global header             ${blue}\n        face global mono               ${base1}\n        face global block              ${cyan}\n        face global link               ${base01}\n        face global bullet             ${yellow}\n        face global list               ${green}\n\n        # builtin\n        face global Default            ${base00},${base3}\n        face global PrimarySelection   ${base3},${blue}+fg\n        face global SecondarySelection ${base1},${base01}+fg\n        face global PrimaryCursor      ${base3},${base00}+fg\n        face global SecondaryCursor    ${base3},${base1}+fg\n        face global PrimaryCursorEol   ${base3},${yellow}+fg\n        face global SecondaryCursorEol ${base3},${orange}+fg\n        face global LineNumbers        ${base1},${base2}\n        face global LineNumberCursor   ${base01},${base2}\n        face global LineNumbersWrapped ${base2},${base2}\n        face global MenuForeground     ${base3},${yellow}\n        face global MenuBackground     ${base01},${base2}\n        face global MenuInfo           ${base1}\n        face global Information        ${base2},${base1}\n        face global Error              ${red},default+b\n        face global DiagnosticError    ${red}\n        face global DiagnosticWarning  ${yellow}\n        face global StatusLine         ${base01},${base2}+b\n        face global StatusLineMode     ${orange}\n        face global StatusLineInfo     ${cyan}\n        face global StatusLineValue    ${green}\n        face global StatusCursor       ${base0},${base03}\n        face global Prompt             ${yellow}+b\n        face global MatchingChar       ${red},${base2}+b\n        face global BufferPadding      ${base1},${base3}\n        face global Whitespace         ${base1}+f\n    \"\n}\n"
  },
  {
    "path": "colors/tomorrow-night.kak",
    "content": "#\n## Tomorrow-night, adapted by nicholastmosher\n##\n\nevaluate-commands %sh{\n    foreground=\"rgb:c5c8c6\" # gui05\n    background=\"rgb:1d1f21\" # gui00\n    selection=\"rgb:373b41\"  # gui02\n    window=\"rgb:282a2e\"     # gui01\n    comment=\"rgb:969896\"    # gui03\n    red=\"rgb:cc6666\"        # gui08\n    orange=\"rgb:de935f\"     # gui09\n    yellow=\"rgb:f0c674\"     # gui0A\n    green=\"rgb:b5bd68\"      # gui0B\n    blue=\"rgb:81a2be\"       # gui0D\n    aqua=\"rgb:8abeb7\"       # gui0C\n    purple=\"rgb:b294bb\"     # gui0E\n\n    ## code\n    echo \"\n        face global value ${orange}\n        face global type ${yellow}\n        face global variable ${red}\n        face global module ${blue}\n        face global function ${blue}\n        face global string ${green}\n        face global keyword ${purple}\n        face global operator ${aqua}\n        face global attribute ${purple}\n        face global comment ${comment}\n        face global documentation comment\n        face global meta ${purple}\n        face global builtin ${yellow}\n    \"\n\n    ## markup\n    echo \"\n        face global title ${blue}\n        face global header ${aqua}\n        face global mono ${green}\n        face global block ${orange}\n        face global link ${blue}\n        face global bullet ${red}\n        face global list ${red}\n    \"\n\n    ## builtin\n    echo \"\n        face global Default ${foreground},${background}\n        face global PrimarySelection ${foreground},${selection}+fg\n        face global SecondarySelection ${foreground},${window}+fg\n        face global PrimaryCursor ${background},${foreground}+fg\n        face global SecondaryCursor ${background},${aqua}+fg\n        face global PrimaryCursorEol ${background},${green}+fg\n        face global SecondaryCursorEol ${background},${green}+fg\n        face global LineNumbers ${comment},${window}\n        face global LineNumberCursor ${yellow},${window}+b\n        face global MenuForeground ${window},${foreground}\n        face global MenuBackground ${foreground},${window}\n        face global MenuInfo ${red}\n        face global Information ${foreground},${window}\n        face global Error ${foreground},${red}\n        face global DiagnosticError ${red}\n        face global DiagnosticWarning ${yellow}\n        face global StatusLine ${foreground},${selection}\n        face global StatusLineMode ${yellow}+b\n        face global StatusLineInfo ${aqua}\n        face global StatusLineValue ${green}\n        face global StatusCursor ${window},${aqua}\n        face global Prompt ${background},${aqua}\n        face global MatchingChar ${yellow},${background}+b\n        face global BufferPadding ${aqua},${background}\n        face global Whitespace ${comment}+f\n    \"\n}\n"
  },
  {
    "path": "colors/zenburn.kak",
    "content": "# zenburn theme\n\nevaluate-commands %sh{\n    # define some named colors\n    zentext=\"rgb:cfcfcf\"\n    zenselectionbg=\"rgb:3f7fcc\"\n    zencursor=\"rgb:2a2a2a,rgb:dfdfbf\"\n    zencursoreol=\"rgb:2a2a2a,rgb:cc9393\"\n    zensecondaryfg=\"rgb:2a2a2a\"\n    zendefault=\"${zentext},rgb:3f3f3f\"\n    zenstatus=\"rgb:efdcbc,rgb:2a2a2a\"\n    zenstatuscursor=\"${zentext},rgb:7f9f7f\"\n    zeninfo=\"rgb:cc9393,rgb:2a2a2a\"\n    zenmenubg=\"rgb:7f9f7f,rgb:4a4a4a\"\n    zenmenufg=\"rgb:8cd0d3,rgb:5b5b5b\"\n    zenkeyword=\"rgb:f0dfaf+b\"\n    zenstorageClass=\"rgb:c3bf9f+b\"\n    zennumber=\"rgb:8cd0d3\"\n    zencomment=\"rgb:7f9f7f\"\n    zenconstant=\"rgb:dca3a3+b\"\n    zenspecial=\"rgb:cfbfaf\"\n    zenfunction=\"rgb:efef8f\"\n    zenstatement=\"rgb:e3ceab\"\n    zenvariable=\"rgb:efdcbc\"\n    zentype=\"rgb:dfdfbf\"\n    zenstring=\"rgb:cc9393\"\n    zenmodule=\"${zenstring}\"\n    zenexception=\"rgb:c3bf9f+b\"\n    zenmatching=\"rgb:3f3f3f,rgb:8cd0d3\"\n    zenpadding=\"rgb:f0dfaf,rgb:343434+b\"\n\n    echo \"\n        # then we map them to code\n        face global value ${zenconstant}\n        face global type ${zentype}\n        face global variable ${zenvariable}\n        face global module ${zenstring}\n        face global function ${zenfunction}\n        face global string ${zenstring}\n        face global keyword ${zenkeyword}\n        face global operator ${zenfunction}\n        face global attribute ${zenstatement}\n        face global comment ${zencomment}\n        face global documentation comment\n        face global meta ${zenspecial}\n        face global builtin default+b\n\n        # and markup\n        face global title ${zenkeyword}\n        face global header ${zenconstant}\n        face global mono ${zennumber}\n        face global block ${zenstatement}\n        face global link ${zenstring}\n        face global bullet ${zenvariable}\n        face global list ${zentype}\n\n        # and built in faces\n        face global Default ${zendefault}\n        face global PrimarySelection ${zentext},${zenselectionbg}+fg\n        face global SecondarySelection ${zensecondaryfg},${zenselectionbg}+fg\n        face global PrimaryCursor ${zencursor}+fg\n        face global SecondaryCursor ${zencursor}+fg\n        face global PrimaryCursorEol ${zencursoreol}+fg\n        face global SecondaryCursorEol ${zencursoreol}+fg\n        face global LineNumbers ${zendefault}\n        face global LineNumberCursor ${zenstatus}\n        face global MenuForeground ${zenmenufg}\n        face global MenuBackground ${zenmenubg}\n        face global MenuInfo rgb:cc9393\n        face global Information ${zeninfo}\n        face global Error default,red\n        face global DiagnosticError red\n        face global DiagnosticWarning yellow\n        face global StatusLine ${zenstatus}\n        face global StatusLineMode ${zencomment}\n        face global StatusLineInfo ${zenspecial}\n        face global StatusLineValue ${zennumber}\n        face global StatusCursor ${zenstatuscursor}\n        face global Prompt ${zenconstant}\n        face global MatchingChar default+b\n        face global BufferPadding ${zenpadding}\n        face global Whitespace ${zensecondaryfg}+f\n    \"\n}\n"
  },
  {
    "path": "contrib/TRAMPOLINE",
    "content": "+=----------------------------------------------------------------------------=+\n              _              _\n             | |            | |\n             | | __   __ _  | | __   ___    _   _   _ __     ___\n             | |/ /  / _` | | |/ /  / _ \\  | | | | | '_ \\   / _ \\\n             |   <  | (_| | |   <  | (_) | | |_| | | | | | |  __/\n             |_|\\_\\  \\__,_| |_|\\_\\  \\___/   \\__,_| |_| |_|  \\___|\n\n                  Mawww's experiment for a better code editor\n+=----------------------------------------------------------------------------=+\n\nThis walk-through is an introduction to Kakoune's basic editing capabilities\nto help new users transition over easily from another editor, or simply\nlearn how to write and edit documents with style.\n\nDuring the learning period, it is useful to activate an automatically displayed\ncontextual help for commands in normal mode: `:set -add global autoinfo normal`\n\nIn the first section, you will learn about the primitives of the editing\nlanguage to be able to get to a level of knowledge of the editor that\nguarantees that you can work with it efficiently.\n\nIn the second section, for users who've gone through the basics and want to\nmove on to more advanced functionalities, we explain other primitives whose\nrole has a less dominant place in an everyday editing session, but still\nprove themselves powerful when used on the right occasion.\n\nFinally, as this document is in no way an exhaustive list of features, don't\nhesitate to check out the official documentation to complement your tool-set,\nask questions to more seasoned users on IRC, and check the documentation\nusing the built-in `:doc` command.\n\n+=--------------------------------=+ BASICS +=--------------------------------=+\n\n                    =[ MODES\n\n                    Kakoune uses a paradigm called \"modal editing\" to allow\n       .---,        users to either have every single key they type inserted\n       | i |        into the file being edited (called \"insert mode\"),\n       `---'        or execute commands that are triggered by the keys hit\n                    (the \"normal mode\").  Aside from arrow keys, most keys\n       .---,        described in this document are \"editing primitives\" that\n       |esc|        have to be hit in normal mode, which is the default mode\n       `---'        when you start the editor. To enter insert mode, hit the\n                    `i` key, and to leave it and return to normal mode, hit the \n                    escape key.\n\n\n                    =[ MOVEMENT\n       .---,\n       | ↑ |        Movement in a buffer (the representation of the contents\n   .---'---'---,    of a file opened by Kakoune) can be achieved using the arrow\n   | ← | ↓ | → |    keys, which will move the cursor up one column/row into\n   `---'---'---`    a given direction.\n\n                    However, furthering the tradition of mode-based editors,\n .---,---,---,---,  the `h`, `j`, `k` and `l` keys can be used for the same\n | h | j | k | l |  purpose, and will respectively move the cursor to the\n `---'---'---'---`  left, down, up, right by one, when hit. Using those keys\n   |   |   |   |    is the recommended way of moving around in a buffer.\n .---,---,---,---,  If you're not familiar with this concept, the proximity\n | ← | ↓ | ↑ | → |  of those four keys with the rest of the lettered keys\n `---'---'---'---`  on a `qwerty` layout allows faster interaction with the\n                    primitives than if the user had to move their hand to\n   .---,            reach the arrow keys.\n   | g |_.\n   `---' |`.---,    Another way of moving the cursor is the \"goto\" utility,\n         | | g |    invoked by hitting the `g` key. A menu will pop up with a\n         | `---'    summary of all the possible keys that can be hit, along with\n          `.---,    the location where they will move the cursor to, but the\n           | e |    most used ones that we are interested in, for now, are `g`\n           `---'    and `e`. The first one will jump to the beginning of the\n                    buffer, and the second one to its end.\n\n\n                    =[ VIEW\n\n   .---,            Displacing the cursor can sometimes move the view into an\n   | v |_.          inconvenient configuration, leaving some necessary context\n   `---' |`.---,    off-screen, or simply feel uncomfortable to type into.\n         | | t |    Kakoune provides a menu (similar to the `goto` menu\n         | `---'    mentioned in the previous section) that allows users to\n         |`.---,    move the current view in relation to the position of the\n         | | b |    cursor. Upon hitting the `v` key, a short menu appears\n         | `---'    which allows us to hit a second key according to how the\n          `.---,    view should be centered vertically: to leave the cursor\n           | v |    respectively on top, at the bottom or in the middle of the\n           `---'    current view, hit the `t`, `b` or `v` keys.\n\n\n                    =[ SEARCH\n\n                    In order to move the cursor to a specific word, the search\n                    command is the way to go. This functionality allows\n        .---,       the user to jump to the next occurrence of a piece of text.\n        | / |       Upon hitting the `/` key, a prompt reading \"search\"\n        `---'       will pop up in the status bar in which you can type\n                    your text and validate using the `<ret>` (return) key.\n     .---, .---,    You'll notice that as you type, the cursor changes location\n     |alt|+| / |    to automatically give you a preview of where the cursor\n     `---' `---'    would be displaced to if you validated the search. However,\n                    this behavior is only a preview, exiting prompt mode with\n                    the `<esc>` (escape) key will leave the current position\n        .---,       of the cursor unchanged. Note that you can also use a\n        | n |       regular expression as input.  By default the search\n        `---'       function will look for results forward, starting from\n                    the current location of the cursor, but you can search\n     .---, .---,    backwards using `<a-/>` (alt + `/`).\n     |alt|+| n |\n     `---' `---'    Jumping from one match to the other forward can be achieved\n                    using the `n` key, and backwards using the `<a-n>` (alt +\n                    `n`) key combination.\n\n\n                    =[ SELECTIONS\n\n                    You have certainly noticed that when searching for\n        .---,       text, the cursor extends to highlight the entire match.\n        | ; |       In fact, what we know in other editors as a \"cursor\" is\n        `---'       actually a single character wide selection in Kakoune,\n                    and can be expanded using primitives. When \"expanded\",\n     .---, .---,    the selection is an area whose beginning is the \"anchor\"\n     |alt|+| ; |    and the end the \"secondary cursor\". To switch anchor and\n     `---' `---'    cursor, use `<a-;>`, and to collapse the selection onto\n                    its anchor, use `;`.\n\n                    Moreover, not only does Kakoune expand the principle of\n                    \"cursor\" by introducing selections, but it also allows\n        .---,       multiple selections within the same buffer. This makes\n        | % |       it very convenient to modify text in multiple locations\n        `---'       at once, as editing primitives apply to all the currently\n                    selected text.\n        .---,\n        | s |       Example: to remove all occurrences of the word \"foo\", one\n        `---'       would select the entire buffer (`%`), select occurrences of\n                    the word (`s`, \"\\bfoo\\b\", `<ret>`), then remove it (`d`).\n\n                    ==[ SELECTING OBJECTS\n\n                    In addition to allowing text selection using regular\n     .---, .---,    expressions, certain objects are defined by default to\n     |alt|+| i |    allow easy selection of text. Objects are bits of text\n     `---' `---'    in the buffer that are identified according to their\n                    structure, rather than their contents, e.g., a paragraph,\n     .---, .---,    a sentence, or a word. When the cursor is located within\n     |alt|+| a |    the boundaries of an object you want to interact with,\n     `---' `---'    several options are available: selecting the contents of an\n                    object without its boundaries (`<a-i>`), a part of it (from\n        .---,       the anchor to its end or its beginning, respectively `]`\n        | ] |       and `[`), or the entire object (`<a-a>`). Those \"selection\n        `---'       ranges\" are the first part of a two stages shortcut,\n                    as once you've used the key that dictates what part of\n        .---,       the object is to be selected, a menu with a description\n        | [ |       of all the object types select-able will be displayed,\n        `---'       giving a summary of all the keys you can hit to complete\n                    the selection procedure.\n\n                    Example: to select the paragraph in which the anchor lies,\n                    invoke the \"inner object selection\" shortcut (`<a-i>`),\n                    locate \"paragraph\" in the information box that pops up and\n    .---,           hit the according key (`p`). The entire two steps sequence\n    | [ |_.         is thus: `<a-i> p`.\n    `---' |`.---,\n          | | ( |   Example: to select everything between the anchor and the\n          | `---'   beginning of the current parenthesis pair, use the selection\n           `.---,   sequence: `[ (`. Note that common objects that use\n            | r |   pairs of opening/closing punctuation signs (brackets,\n            `---'   braces, quotes etc) have an alternative second key that\n                    is displayed in the information menu that you can use to\n                    minimize finger gymnastics. The previous shortcut could\n                    thus also be written: `[ b`.\n\n                    ==[ MOVEMENT SELECTIONS\n\n                    If objects are an easy way to select content-agnostic\n    .---,           data in a buffer, they can also be seen as a way to move\n    | [ |_.         about the buffer. As selecting objects will displace the\n    `---'  `.---,   anchor into a given direction, you can wrap or move around\n            | p |   particular chunks of text without using the conventional\n            `---'   means (e.g., arrow keys or jumps), turning them partially\n                    into movement primitives.\n    .---,\n    | ] |_.         Example: one of the most used object selection combination\n    `---'  `.---,   is the \"object end/begin paragraph\" one: using `[` or\n            | p |   `]` will displace the anchor into a given direction, and\n            `---'   applying that to the paragraph object allows \"jumping\"\n                    from one newline separated block of text to another.\n                    The resulting shortcut is thus: `] p` to move forward, or\n                    `[ p` to move backward.\n\n\n                    =[ FILTERING A SELECTION\n\n                    Selecting an entire buffer (`%`) or parts of it (`s`) is a\n                    natural and basic operation in a typical editing session,\n     .---, .---,    however, there are some cases where we need to be able to\n     |alt|+| k |    drop some selections arbitrarily, as opposed to trying\n     `---' `---'    to select the ones we need directly. This concept becomes\n                    very useful when coming up with a regular expression for\n     .---, .---,    the basic selection primitive (`s`) is too tedious (if\n     |alt|+| K |    even possible), that's why the editor provides us with a\n     `---' `---'    \"keep matching\" and a \"keep not matching\" operations,\n                    in order to respectively keep exclusively the selections\n                    who match or do not match a given regular expression.\n\n                    Example: when parsing a log file whose lines follow the\n                    usual log pattern (e.g. \"[1484383442] ERROR: some data\"),\n                    we want to be able to select all the lines individually\n     .---, .---,    (`%`, `<a-s>` to split all the lines), keep those that\n     |alt|+| s |    start with a bracketed time-stamp (`<a-k>^\\[`), but\n     `---' `---'    exclude the debug messages (`<a-K>DEBUG`). Of course,\n                    it's possible to come up with a regular expression to\n                    match those simple requirements, but it would take more\n                    work to write it than to organically apply filters on a\n                    general selection, individually.\n\n\n                    =[ SELECTION DUPLICATION\n\n        .---,       Duplicating content can be achieved using a widely\n        | y |       implemented concept: yanking and pasting. Yanking the\n        `---'       current selection (`y`) into the copy register allows the\n        .---,       user to subsequently insert the copied text in the buffer\n        | p |       (`p`).\n        `---'\n        .---,       Note that the default \"paste\" primitive will insert the\n        | P |       contents of the copy register after the current selection,\n        `---'       if you want copied text to be inserted before the current\n                    selection then you can use the `P` key.\n\n\n                    =[ DELETING / REPLACING SELECTIONS\n\n                    Text replacement is a two-step process in Kakoune, which\n        .---,       involves selecting text to be replaced, and then erasing it\n        | d |       to insert the replacement text. After selections have been\n        `---'       made, you can simply hit the deletion primitive (`d`), then\n        .---,       either enter insert mode to write down the replacement text\n        | c |       (`i`), or stay in command mode to paste the replacement\n        `---'       text stored in the copy register. As deleting and entering\n        .---,       insert mode can be redundant, a primitive that implements\n        | R |       deletion followed by insert mode entrance was implemented:\n        `---'       `c`. You can also directly replace the current selection\n                    with the content of the copy register using a primitive\n                    also implemented for that purpose: `R`.\n\n\n                    =[ UNDO / REDO\n\n                    Mistakes or wrong decisions can happen while editing.\n        .---,       To go back to earlier states of the buffer, you can press\n        | u |       the `u` key several times. On the contrary, pressing `U`\n        `---'       allows traveling forward in the history tree.\n\n\n+=-------------------------------=+ ADVANCED +=-------------------------------=+\n\n                    =[ SPLITTING\n\n                    The selection primitive (`s`) is a powerful tool to select\n                    chunks of data, but sometimes the format of said data isn't\n        .---,       uniform enough to allow creating clear cut selections. In\n        | S |       order to avoid having to write overly complicated regular\n        `---'       expressions that select precisely the wanted text, the\n                    splitting primitive (`S`) allows applying a delimiter to\n                    the current selection, splitting it into separate chunks.\n\n                    Example: selecting the items in a CSV-style list (e.g.,\n                    \"1,2,3,4\") is as simple as selecting the line, then\n                    splitting it using the comma separator (`S,`). Note that\n                    more advanced splitting is possible, since the delimiter\n                    passed to this primitive is a regular expression.\n\n\n                    =[ ROTATING\n\n                    Often used in conjunction with the splitting primitive\n                    (`S`), the rotation primitive (`<a-)>`) shifts all the\n                    selections clock-wise. Note that a count (described after)\n                    allows the rotation to take place in sub-groups whose size\n     .---, .---,    is given by the count parameter.\n     |alt|+| ) |\n     `---' `---'    Example: in a numbered list where all the numbers are\n                    selected (e.g., `1 2 3 4 5 6 7 8 9 0`), a rotation using\n                    this primitive will shift all the numbers by one selection\n                    forward, while leaving the original multiple selections\n                    untouched (e.g., `0 1 2 3 4 5 6 7 8 9`).\n\n\n                    =[ COUNTS\n\n   .---,            In order to pass a count to a primitive, simply type the\n   |0-9|_.          number out before hitting the primitive key/combination.\n   `---' |`.---,    Counts allow primitives to specialize or extend their\n         | | g |    original functionality by using it as a parameter,\n         | `---'    acting on their side effect.\n         |`.---,\n         | | G |    Example: in order to respectively jump or select up to a\n         | `---'    particular line, pass the line number to the `g` or `G`\n         |`.---,    primitives (e.g., `42g` or `7G`).\n         | | o |\n         | `---'    Example: creating an arbitrary amount of new lines\n          `.---,    above or below the current line and spawning a new selection\n           | O |    for each of them is achieved by passing the number of lines\n           `---'    as a count respectively to the `o` and `O` primitives.\n\n\n                    =[ REGISTERS\n\n                    Similarly to counts, registers influence the behavior of\n   .---,            certain primitives. They are storage structures identified\n   | \" |_.          by a single character, and are populated by primitives as a\n   `---'  `.---,    result of a side effect. Although primitives populate a\n           |a-z|    specific register by default, it's possible to modify which\n           `---'    is going to be populated upon execution using the double\n                    quote (`\"`) primitive, and subsequently hitting a key that\n        .---,       will serve as an identifier.\n        | * |\n        `---'       Example: the smart search primitive (`*`) uses the current\n                    selection as a search pattern, which will be saved to the\n   .---,            `/` register. In order to use this primitive to execute a\n   | \" |_. .---,    temporary search, one could make this primitive save the\n   `---'  `| _ |    pattern to a different register, to preserve the default one,\n           `---'    e.g., `\"m*` to save the pattern to the `m` register, or even\n                    `\"_*` to save the pattern to a \"null\" register, which does \n                    not store anything written to it.\n\n                    ==[ CAPTURE GROUPS\n\n                    Although registers can pass as mere buffer metadata,\n     .---, .---,    they are an integral part of an editing session. The\n     |ctl|+| r |    `<c-r>` key combination allows to insert into the buffer\n     `---' `---'    the value of a register, whose identifier is typed right\n                    after the combination.\n\n.---, .---,         Example: inserting the name of the current buffer in insert\n|ctl|+| r |_.       mode can be achieved using the `%` register, which holds\n`---' `---'  `.---, this information: `<c-r>%`.\n              | % |\n              `---' Other registers that are set automatically are\n                    the numbered registers, which hold the values of the groups\n                    matched in the last search or select operation (`/` and\n.---, .---,         `s` primitives).\n|ctl|+| r |_.\n`---' `---'  `.---, Example: when using the search primitive (`/`) with a\n              |0-9| regular expression containing groups to match a list of\n              `---' first and last names (e.g., `(\\w+) (\\w+)` on `John Doe`),\n                    issuing `<c-r>1` would insert the first name (`John`),\n                    and `<c-r>2` the last name (`Doe`).\n\n\n                    =[ CUSTOM SELECTIONS\n\n                    Despite the ability to select bits of data using regular\n                    expressions, there are times when using them isn't enough,\n                    and additional manual editing of the selections is\n        .---,       needed. In order to loop through all the selections and\n        | ) |       remove the current one, two primitives are available:\n        `---'       respectively the parenthesis (`)`), and the alt/comma\n                    key combination (`<a-,>`).\n     .---, .---,\n     |alt|+| , |    Example: given a list of three numbers all selected\n     `---' `---'    individually, (e.g., `1 2 3`), deselecting the second\n                    selection would be done by hitting the parenthesis primitive\n                    (`)`) until the according selection is the current one,\n                    then hitting `<a-,>` to end up with only the first\n                    and third number selected.\n\n                    However, being able to trim out some selections out\n        .---,       of a bigger set isn't always convenient, as it doesn't\n        | ^ |       allow more advanced constructs such as combining sets of\n        `---'       multiple-selections that result from different regular\n        .---,       expressions. To allow that, the save mark (`Z`) and append\n        | Z |       mark (`<a-z>`) come in handy, as they respectively save\n        `---'       the current selection to the mark register (`^`), and\n                    show a menu that allows appending the current selection\n.---, .---,         to the mark register upon hitting the `a` key. That way,\n|alt|+| z |_.       it becomes possible to chain and save (append) several\n`---' `---'  `.---, selections made using completely different methods\n              | a | (select, split etc) without being forced to preserve\n              `---' them at all times.\n        .---,\n        | z |       Restoring a mark saved to the mark register using those\n        `---'       primitives can be achieved by using the restore mark\n                    primitive (`z`).\n\n\n                    =[ LEVERAGING SHELL COMMANDS\n\n                    UNIX systems provide with some tools whose purpose is\n                    to interact with raw data, and being a UNIX compliant\n        .---,       aspiring tool itself, Kakoune allows leveraging those\n        | | |       tools to modify a buffer's contents. Upon invoking the pipe\n        `---'       primitive (`|`), an input field pops up which prompts for\n                    a shell command, to which the selections will individually\n                    be sent through the command's standard input.\n\n                    Example: wrapping a selection can be achieved by invoking\n                    the `fold` utility, e.g., `|fold -w80`. You could also want\n                    to see a patch of all the modifications made to the buffer\n                    since it was last saved: `%|diff -u <c-r>% -`. Note that\n                    the `<c-r>%` has to be typed interactively, as it will\n                    insert the name of the buffer into the command.\n\n                    Another equally useful primitive that doesn't depend on\n        .---,       the contents of the current selections is the exclamation\n        | ! |       mark primitive (`!`), which simply insert the output of\n        `---'       the given shell command before each selection.\n\n                    Example: in order to insert the date of the day at the\n                    beginning of the current buffer, one could use `gg`\n                    followed with `!date`.\n\n                    But not all shell-related primitives insert data into\n                    the current buffer, the `$` key is in fact a way to\n        .---,       apply a predicate to all selections, in order to filter\n        | $ |       them out. The command passed to this primitive will be\n        `---'       executed in a new shell using each individual selection for\n                    context, which will either be kept if the command returned\n                    a successful exit code (zero) or dropped otherwise (any\n                    non-zero value).\n\n                    Example: after selecting all the lines in a buffer and\n                    splitting them individually (`%`, `<a-s>`), keeping every\n                    odd-numbered line can be achieved with the following\n                    sequence: `$` `[ $((kak_reg_hash)) -ne 0 ]`.\n\n\n                    =[ REPEATING ACTIONS\n\n                    ==[ PUNCTUAL INTERACTIONS\n\n                    In order to modify text efficiently or insert redundant\n                    bits of data, two primitives are available. The dot `.`\n        .---,       primitive repeats the last change that was made in insert\n        | . |       mode (e.g., writing down text after hitting the insert\n        `---'       primitive `i`). Similarly, repeating the last selection\n                    (e.g., make with the find primitive `f`) can be achieved\n                    using the `<a-.>` primitive.\n\n                    Example: to select a paragraph to append a newline\n     .---, .---,    character to it and cycle through the following paragraphs\n     |alt|+| . |    to repeat the same insertion an arbitrary amount of times,\n     `---' `---'    one would first select the paragraph with `]p`, append a\n                    newline to it `a<ret><esc>`, then repeat both operations\n                    as needed with `<a-.>` and `.` respectively.\n\n                    ==[ COMPLEX CHANGES\n\n                    Transforming successive chunks of formatted data can\n        .---,       be cumbersome when done manually, and lack hindsight\n        | q |       when writing a script for that particular purpose\n        `---'       non-interactively. The middle ground between the two\n        .---,       solutions is to record the modifications made to one\n        | Q |       chunk interactively, and replay the sequence of keys\n        `---'       at will. The sequence in question is a macro: the `Q`\n                    primitive will create a new one (i.e., record all the keys\n.---, .---,         hit henceforth until the escape key `<esc>` is hit), and\n|ctl|+| r |_.       the `q` primitive will replay the keys saved in the macro.\n`---' `---'  `.---,\n              | @ | Notes: macros can easily be translated into a proper\n              `---' script, as they are saved in the `@` register, which you\n                    can insert into a buffer using `<c-r>@`.\n"
  },
  {
    "path": "contrib/Tupfile",
    "content": "##\n## Tupfile for kakoune\n## by lenormf\n##\n## How to use:\n## Initialize a tup database in the main directory with `tup init`\n## Create a symlink from `contrib/Tupfile` to `src/Tupfile`\n## Start the build with the `tup` command\n##\n\n.gitignore\n\ndebug    = yes\nstatic   = no\n\nCXX      = g++\nCXXFLAGS = -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address\nCPPFLAGS =\nLDFLAGS  =\nLIBS     =\n\nifeq ($(debug),yes)\n    CPPFLAGS += -DKAK_DEBUG\n    CXXFLAGS += -g\n    suffix = .debug\nelse\n    ifeq ($(debug),no)\n        CXXFLAGS += -O3\n        suffix = .opt\n    else\n        error debug should be either yes or no\n    endif\nendif\n\nifeq ($(static),yes)\n    PKG_CONFIG_FLAGS += --static\n    LDFLAGS += -static -pthread\nendif\n\nifneq (@(TUP_PLATFORM),macosx)\n    ifeq (@(TUP_PLATFORM),win32)\n        LIBS += -lncursesw -ldbghelp\n        CPPFLAGS += -D_XOPEN_SOURCE=700\n    else\n        LDFLAGS += -rdynamic\n    endif\nendif\n\nifeq ($(CXX),clang++)\n    CXXFLAGS += -frelaxed-template-template-args\nendif\nifeq ($(CXX),g++)\n    CXXFLAGS += -Wno-init-list-lifetime\nendif\n\n!cxx = |> ^ CC %f^ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -MD -MP -MF %O.d -c %f -o %o |>\n!ld = |> ^ LD %o^ $(CXX) $(LDFLAGS) %f $(LIBS) -o %o |>\n!ln = |> ^ LN %o^ ln -sf %f %o |>\n\n: foreach *.cc |> !cxx |> .%B$(suffix).o | .%B$(suffix).d {objs}\n: |> ^ MK %o^ printf \"%%s\" \"namespace Kakoune { const char* version = \\\"`if [ -f .version ]; then cat .version; elif  [ -d ../.git ]; then git describe --tags HEAD; else echo \\\"unknown\\\"; fi`\\\"; }\" > .version.cc.tmp; if cmp -s .version.cc.tmp .version.cc; then rm .version.cc.tmp; else mv .version.cc.tmp .version.cc; fi |> .version.cc\n: .version.cc |> ^ CC %f^ $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c %f -o %o |> %B.o\n: {objs} .version.o |> !ld |> kak$(suffix)\n: kak$(suffix) |> !ln |> kak\n"
  },
  {
    "path": "contrib/describe_sessions.sh",
    "content": "#!/bin/sh\n##\n## describe_sessions.sh for kakoune\n## by lenormf\n##\n\nreadonly KAK_SCRIPT='\n    {\n        echo\n\n        printf \"Session: %s\\n\" \"${kak_session}\"\n        printf \"Current working directory: %s\\n\" \"${PWD}\"\n\n        eval set -- \"${kak_buflist}\"\n        printf \"Buffers (%d):\\n\" $#\n        for bufname in \"$@\"; do\n            printf \"\\t%s\\n\" \"${bufname}\"\n        done\n\n        eval set -- \"${kak_client_list}\"\n        printf \"Clients (%d):\\n\" $#\n        for clientname in \"$@\"; do\n            printf \"\\t%s\\n\" \"${clientname}\"\n        done\n    } >>{{outfile}}\n\n    rm -rf {{sentinel}}\n'\n\nmain() {\n    outfile=$(mktemp \"${TMPDIR:-/tmp}\"/kak-describe_sessions.XXXXXXXX)\n    nb_sessions=0\n    nb_dead_sessions=0\n    nb_suspended_sessions=0\n\n    if ! command -v socat >/dev/null 2>&1; then\n        echo \"Unmet dependency: socat\" >&2\n        exit 1\n    fi\n\n    script=$(printf 'nop %%sh{ %s }' \"${KAK_SCRIPT}\" | sed \"s,{{outfile}},\\\"${outfile}\\\",g\")\n\n    sessions_dir=\"${TMPDIR:-/tmp}/kakoune/${USER}\"\n    if [ -n \"${XDG_RUNTIME_DIR}\" ]; then\n        sessions_dir=\"${XDG_RUNTIME_DIR}/kakoune\"\n    fi\n\n    for session in \"${sessions_dir}\"/*; do\n        name_session=\"${session##*/}\"\n\n        if printf '' | socat - UNIX-CONNECT:\"${session}\",connect-timeout=1 2>/dev/null; then\n            sentinel=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-sentinel.XXXXXXXX)\n            script_session=$(printf %s \"${script}\" | sed \"s,{{sentinel}},\\\"${sentinel}\\\",g\")\n\n            if ! printf %s \"${script_session}\" | kak -p \"${name_session}\"; then\n                printf '\\nSession \"%s\" dead\\n' \"${name_session}\" >> \"${outfile}\"\n                nb_dead_sessions=$((nb_dead_sessions + 1))\n                continue\n            fi\n\n            wait_limit=2\n            while ! mkdir \"${sentinel}\" 2>/dev/null && [ \"${wait_limit}\" -gt 0 ]; do\n                wait_limit=$((wait_limit - 1))\n                sleep 1\n            done\n\n            rm -rf \"${sentinel}\"\n\n            if [ \"${wait_limit}\" -gt 0 ]; then\n                nb_sessions=$((nb_sessions + 1))\n            else\n                printf '\\nSession \"%s\" suspended\\n' \"${name_session}\" >> \"${outfile}\"\n                nb_suspended_sessions=$((nb_suspended_sessions + 1))\n            fi\n        else\n            nb_dead_sessions=$((nb_dead_sessions + 1))\n        fi\n    done\n\n    printf 'Running sessions: %d\\nSuspended sessions: %d\\nDead sessions: %d\\n' \\\n        \"${nb_sessions}\" \"${nb_suspended_sessions}\" \"${nb_dead_sessions}\"\n    cat \"${outfile}\"\n\n    rm -f \"${outfile}\"\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "contrib/gendocs.py",
    "content": "#!/usr/bin/env python3\n\n# This script generates a static documentation web site\n# by parsing the `**/*.asiidoc` files from the repository.\n#\n# Dependencies:\n# * Python 3\n# * `xdg-open` for opening the final result in a web browser.\n# * `antora` - a static documentation web site generator,\n#     https://docs.antora.org/antora/latest\n#\n# Usage:\n# ```console\n# $ ./contrib/gendocs.py\n# ```\n#\n# After running it should open the generated web site in a browser.\n#\n\n\nfrom dataclasses import dataclass\nfrom typing import Optional\nimport glob\nimport itertools\nimport os\nimport pathlib\nimport re\nimport shutil\nimport subprocess\n\n# Get the script directory.\nscript_dir = os.path.dirname(os.path.realpath(__file__))\n\n# Switch to the projects root dir.\nos.chdir(os.path.join(script_dir, \"..\"))\n\n# Recreating the final output dir to start from scratch.\nshutil.rmtree(\"doc_gen\", ignore_errors=True)\nos.makedirs(\"doc_gen\", exist_ok=True)\n\n# Antora fails if the repo contains broken symbolic links.\n# shutil.rmtree(\"libexec\", ignore_errors=True)\n\n# Canonical Antora paths.\n# See: https://docs.antora.org/antora/latest/standard-directories.\n#      https://docs.antora.org/antora/latest/root-module-directory.\nos.makedirs(\"doc_gen/modules/ROOT/images\", exist_ok=True)\nos.makedirs(\"doc_gen/modules/ROOT/pages\", exist_ok=True)\n\n\n# Put necessary images to the Antora canonical directory.\n# See: https://docs.antora.org/antora/latest/images-directory.\nfor gif_file in glob.glob(\"doc/*.gif\"):\n    shutil.copy(gif_file, \"doc_gen/modules/ROOT/images/\")\n\n\n# Fix links according to the Antora specification.\n# See: https://docs.antora.org/antora/latest/page/xref.\ndef fix_links(path):\n    @dataclass\n    class Link:\n        path: Optional[str]\n        file: Optional[str]\n        fragment: Optional[str]\n        title: str\n\n        def __init__(self, path, file, fragment, title):\n            self.path = path\n            self.file = file\n            self.fragment = fragment\n            self.title = title\n\n    def dropwhile(predicate, string):\n        return \"\".join(\n            itertools.dropwhile(\n                predicate,\n                string,\n            )\n        )\n\n    def dropwhileright(predicate, string):\n        return \"\".join(\n            reversed(\n                list(\n                    itertools.dropwhile(\n                        predicate,\n                        reversed(string),\n                    )\n                )\n            )\n        )\n\n    def takewhile(predicate, string):\n        return \"\".join(\n            itertools.takewhile(\n                predicate,\n                string,\n            )\n        )\n\n    def untag(string):\n        no_opening = dropwhile(lambda c: c == \"<\", string)\n        no_closing = dropwhileright(lambda c: c == \">\", no_opening)\n        return no_closing\n\n    def parse(string):\n        untagged = untag(string)\n        prefix, title = untagged.split(\",\", 1)\n        title = title.strip()\n        fragment = dropwhile(lambda c: c != \"#\", prefix)\n        fragment = fragment if fragment else None\n        fragmentless = takewhile(lambda c: c != \"#\", prefix)\n        segments = fragmentless.split(\"/\")\n        path, file = (\n            (\"/\".join(segments[:-1]), segments[-1])\n            if \"/\" in fragmentless\n            else (None, fragmentless)\n        )\n        return Link(path, file, fragment, title)\n\n    def render(link):\n        if link.path and link.file and link.fragment == \"#\":\n            return f\"xref:{link.path}/{link.file}.adoc[{link.title}]\"\n        elif link.path and link.file and link.fragment:\n            return f\"xref:{link.path}/{link.file}.adoc{link.fragment}[{link.title}]\"\n        elif not link.path and link.file and link.fragment == \"#\":\n            return f\"xref:./{link.file}.adoc[{link.title}]\"\n        elif not link.path and link.file and link.fragment:\n            return f\"xref:./{link.file}.adoc{link.fragment}[{link.title}]\"\n        elif not link.path and link.file and not link.fragment:\n            return f\"<<{link.file},{link.title}>>\"\n        else:\n            raise RuntimeError(f\"Failed to render link: {link}\")\n\n    def process(m):\n        string = m.group(0)\n        return render(parse(string)) if \",\" in string else string\n\n    content = None\n\n    with open(path, \"r\") as file:\n        content = file.read()\n\n    # Fix image links according the Antora specification.\n    # See: https://docs.antora.org/antora/latest/page/image-resource-id-examples.\n    content = content.replace(\"image::doc/\", \"image::\")\n\n    with open(path, \"w\") as file:\n        file.write(re.sub(r\"<<[^>]+>>\", process, content))\n\n\n# A useful documentation page.\n# Add the `.adoc` extension to include it into the result.\nshutil.copy(\n    \"VIMTOKAK\",\n    \"doc_gen/modules/ROOT/pages/VIMTOKAK.adoc\",\n)\nfix_links(\"doc_gen/modules/ROOT/pages/VIMTOKAK.adoc\")\n\nfor source in glob.glob(\"**/*.asciidoc\", recursive=True):\n    # Create directories structure matching the project's original structure.\n    # See: https://docs.antora.org/antora/latest/pages-directory.\n    page_dir = os.path.join(\n        \"doc_gen/modules/ROOT/pages\",\n        os.path.dirname(source),\n    )\n    os.makedirs(page_dir, exist_ok=True)\n\n    # Copy the `asciidoc` file into the Antora `pages` directory\n    # with the mandatory `.adoc` filename extension.\n    adoc = os.path.join(page_dir, pathlib.Path(source).stem + \".adoc\")\n    shutil.copy(source, adoc)\n\n    if source == \"README.asciidoc\":\n        # Update the filename so it reflects the content.\n        # The filename is used for navigation links.\n        shutil.move(\n            \"doc_gen/modules/ROOT/pages/README.adoc\",\n            \"doc_gen/modules/ROOT/pages/index.adoc\",\n        )\n        adoc = \"doc_gen/modules/ROOT/pages/index.adoc\"\n    elif source == \"test/README.asciidoc\":\n        # The file name is used for navigation links.\n        # Update so it reflects the content.\n        shutil.move(\n            \"doc_gen/modules/ROOT/pages/test/README.adoc\",\n            \"doc_gen/modules/ROOT/pages/test/tests.adoc\",\n        )\n        adoc = \"doc_gen/modules/ROOT/pages/test/tests.adoc\"\n    fix_links(adoc)\n\n\n# A navigation file for the sidebar.\n# See: https://docs.antora.org/antora/latest/navigation/single-list.\n#\n# TODO: Generate automatically.\nnav_content = \"\"\"\n* xref:index.adoc[Getting Started]\n* xref:doc/pages/commands.adoc[Commands]\n* xref:doc/pages/expansions.adoc[Expansions]\n* xref:doc/pages/execeval.adoc[Exec Eval]\n* xref:doc/pages/scopes.adoc[Scopes]\n* xref:doc/pages/faces.adoc[Faces]\n* xref:doc/pages/buffers.adoc[Buffers]\n* xref:doc/pages/registers.adoc[Registers]\n* xref:doc/pages/mapping.adoc[Mapping]\n* xref:doc/pages/hooks.adoc[Hooks]\n* xref:doc/pages/command-parsing.adoc[Command Parsing]\n* xref:doc/pages/keys.adoc[Keys]\n* xref:doc/pages/regex.adoc[Regex]\n* xref:doc/pages/options.adoc[Options]\n* xref:doc/pages/highlighters.adoc[Highlighters]\n* xref:doc/pages/modes.adoc[Modes]\n* xref:doc/pages/keymap.adoc[KEYMAP]\n* xref:doc/pages/faq.adoc[FAQ]\n* xref:doc/pages/changelog.adoc[Changelog]\n* xref:doc/design.adoc[Design]\n* xref:doc/coding-style.adoc[Coding Style]\n* xref:doc/writing_scripts.adoc[Writing Scripts]\n* xref:doc/json_ui.adoc[JSON UI]\n* xref:doc/autoedit.adoc[Autoedit]\n* xref:doc/interfacing.adoc[Interfacing]\n* xref:rc/tools/lint.adoc[Linting]\n* xref:rc/tools/autorestore.adoc[Autorestore]\n* xref:rc/tools/doc.adoc[Doc]\n* xref:test/tests.adoc[Tests]\n* xref:VIMTOKAK.adoc[Vi(m) to Kakoune]\n\"\"\"\n\nwith open(\"doc_gen/modules/ROOT/nav.adoc\", \"w\") as f:\n    f.write(nav_content)\n\n# Antora component description file.\n# See: https://docs.antora.org/antora/latest/component-version-descriptor.\nantora_yml_content = \"\"\"\nname: Kakoune\nnav:\n  - modules/ROOT/nav.adoc\ntitle: Kakoune\nversion: latest\n\"\"\"\n\nwith open(\"doc_gen/antora.yml\", \"w\") as f:\n    f.write(antora_yml_content)\n\n# Antora playbook file.\n# See: https://docs.antora.org/antora/latest/playbook.\nantora_playbook_content = \"\"\"\nasciidoc:\n  attributes:\n\n    # Do not complain on missing attributes,\n    # TODO: fix and turn to a fatal warning\n    attribute-missing: skip\n\n    # To fix links\n    idprefix: \"\"\n\n    # To fix links\n    idseparator: \"-\"\n\n    # Better to be reproducible, in general\n    reproducible: true\n\n    # More convenient to turn sections to IDs\n    sectids: true\n\n    # More convenient to have sections as links\n    sectlinks: true\n\n    # Do not want to miss something\n    sectnumlevels: 5\n\n    # More convenient to number the sections\n    sectnums: true\n\n    # Do not want to miss something\n    toclevels: 5\n\n  sourcemap: true\n\ncontent:\n  sources:\n  - url: ./..\n    start_path: doc_gen\n    branches: [\"HEAD\"]\n\nruntime:\n  cache_dir: ./doc_gen/cache # More convenient for the development\n  fetch: true # More convenient for the development\n\n  log:\n    failure_level: fatal\n    level: warn\n\noutput:\n  clean: true # More convenient for the development\n  dir: ./build # Simpler to have it explicit in code\n\nsite:\n  title: Kakoune Docs\n\nui:\n  bundle:\n    url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable\n\"\"\"\n\nwith open(\"doc_gen/antora-playbook.yml\", \"w\") as f:\n    f.write(antora_playbook_content)\n\n# Finally, generate the documentation,\n# results will be saved to the output directory\n# as specified in the `antora-playbook.yml`.\nuse_npx = shutil.which(\"antora\") is None and shutil.which(\"npx\") is not None\nsubprocess.run(([\"npx\"] if use_npx else []) + [\"antora\", \"generate\", \"doc_gen/antora-playbook.yml\"])\nsubprocess.run([\"xdg-open\", \"./doc_gen/build/Kakoune/latest/index.html\"])\n"
  },
  {
    "path": "contrib/kakmap.rb",
    "content": "#!/usr/bin/env ruby\n\n# Generate a reference sheet for Kakoune's normal mode\n# Use: ./kakmap.rb ../src/normal.cc\n\nrequire 'markaby'\n\n# Relies on the keymap HashMap assignment ending with };\nraw = ARGF.read.split( /const\\s+HashMap<Key,\\s*NormalCmd>\\s+keymap\\s*{/ ).last.split( /^};$/ ).first\n\ncommands = {}\n\nraw.split( /\\n+/ ).each{ |line|\n  # skip empty or comment line\n  line = line.strip\n  if line.empty? or /^\\/\\// =~ line\n    next\n  end\n\n  # match key mapping line\n  /^\\{\\s*\\{(?<mdky>[^}]+)\\}\\s*,\\s*\\{\\s*\"(?<dsc>[^\"]+)\"/.match(line) do |m|\n    modAndKey = m['mdky']\n    des = m['dsc']\n\n    modAndKey.gsub!(/\\s*\\/\\*[^*]+\\*\\/\\s*/, '') # remove comment in key definition\n\n    # match key and modifier\n    /Key::(?<key>\\w+)|(?<mod>alt|ctrl)\\('\\\\?(?<key>.+?)'\\)|'\\\\?(?<key>.+?)'$/.match(modAndKey) do |sm|\n      key = sm['key']\n      mod = (sm['mod'] || 'none').to_sym\n\n      key = 'Space' if key == ' '\n      commands[key] ||= {}\n      commands[key][mod] = des\n    end\n  end\n}\n\n# sort, showing single characters first, symbols next and spelled out keys last\ncommands = commands.sort_by{ |key, _|\n  case key\n  when /^\\w$/\n    key.upcase + key.swapcase\n  when /^\\W$/\n    '_' + key\n  else\n    '~~' + key\n  end\n}\n\nputs Markaby::Builder.new {\n  html do\n    head do\n      title \"Kakoune default keymap\"\n    end\n    body do\n      table :style => \"border-collapse: collapse\" do\n        thead do\n          tr do\n            th \"Key\"\n            th \"Description\"\n            th \"ALT + key\"\n            th \"CTRL + key\"\n          end\n        end\n        tbody do\n          for key, binding in commands\n            tr :style => \"border-bottom: 1px solid #fbfbfb\" do\n              th key\n              td binding[:none]\n              td binding[:alt]\n              td binding[:ctrl]\n            end\n          end\n        end\n      end\n    end\n  end\n}\n\n"
  },
  {
    "path": "contrib/kakoune.spec",
    "content": "%bcond_without tests\n\n# Enable LTO. Profit ~8%\n%global optflags        %{optflags} -flto\n%global build_ldflags   %{build_ldflags} -flto\n\nName:           kakoune\nVersion:        2020.09.01\nRelease:        2%{?dist}\nSummary:        Code editor heavily inspired by Vim\n\nLicense:        Unlicense\nURL:            https://kakoune.org/\nSource0:        https://github.com/mawww/kakoune/archive/v%{version}/%{name}-%{version}.tar.gz\n\nBuildRequires:  asciidoc\nBuildRequires:  gcc-c++ >= 7\nBuildRequires:  glibc-langpack-en\nBuildRequires:  pkgconfig(ncurses) >= 5.3\n\n%description\n- Modal editor\n- Faster as in fewer keystrokes \n- Multiple selections\n- Orthogonal design\n\nKakoune is a code editor that implements Vi’s \"keystrokes as a text editing\nlanguage\" model. As it’s also a modal editor, it is somewhat similar to the Vim\neditor (after which Kakoune was originally inspired).\n\nKakoune can operate in two modes, normal and insertion. In insertion mode, keys\nare directly inserted into the current buffer. In normal mode, keys are used to\nmanipulate the current selection and to enter insertion mode.\n\nKakoune has a strong focus on interactivity, most commands provide immediate and\nincremental results, while still being competitive (as in keystroke count) with\nVim.\n\nKakoune works on selections, which are oriented, inclusive ranges of characters.\nSelections have an anchor and a cursor. Most commands move both of\nthem except when extending selections, where the anchor character stays fixed and\nthe cursor moves around.\n\n\n%prep\n%autosetup -p1\n\n# Use default Fedora build flags\nsed -i '/CXXFLAGS += -O3/d' src/Makefile\n\n# Install doc files in proper location\nsed -i 's|$(PREFIX)/share/doc/kak|$(PREFIX)/share/doc/%{name}|' src/Makefile\n\n\n%build\n%set_build_flags\npushd src\n%make_build\npopd\n\n\n%install\npushd src\n%make_install PREFIX=%{_prefix}\npopd\n\n\n%if %{with tests}\n%check\n%set_build_flags\npushd src\nLANG=en_US.utf8 %make_build test\npopd\n%endif\n\n\n%files\n%license UNLICENSE\n%doc README.asciidoc CONTRIBUTING VIMTOKAK\n%{_bindir}/kak\n%{_datadir}/kak/\n%{_mandir}/man1/*\n%{_libexecdir}/kak/\n\n\n%changelog\n* Fri Jan 1 2021 Jiri Konecny <jkonecny@redhat.com> - 2020.09.01-2\n- Add new libexec dir to the spec file\n\n* Wed Sep 2 2020 Jiri Konecny <jkonecny@redhat.com> - 2020.09.01-1\n- Update to 2020.09.01\n\n* Tue Aug 4 2020 Jiri Konecny <jkonecny@redhat.com> - 2020.08.04-1\n- Update to 2020.08.04\n\n* Thu Jan 16 2020 Artem Polishchuk <ego.cordatus@gmail.com> - 2020.01.16-1\n- Update to 2020.01.16\n\n* Tue Dec 10 2019 Artem Polishchuk <ego.cordatus@gmail.com> - 2019.12.10-1\n- Update to 2019.12.10\n\n* Tue Nov 26 2019 Artem Polishchuk <ego.cordatus@gmail.com> - 2019.07.01-4\n- Add patch to pass tests with default Fedora build flags\n\n* Fri Nov 22 2019 Artem Polishchuk <ego.cordatus@gmail.com> - 2019.07.01-2\n- Packaging fixes\n\n* Wed Apr 24 2019 Jiri Konecny <jkonecny@redhat.com> - v2019.01.20-1\n- Add a new build dependency (glibc-langpack-en) required for Fedora 30 and later\n- Update version\n\n* Fri Oct 12 2018 Jiri Konecny <jkonecny@redhat.com> - v2018.09.04-1\n- Update spec file to a new release\n\n* Sat May 5 2018 Łukasz Jendrysik <scadu@disroot.org> - v2018.04.13\n- Use tagged release\n\n* Wed May 11 2016 jkonecny <jkonecny@redhat.com> - 0-208.20160511git84f62e6f\n- Add LANG=en_US.UTF-8 to fix tests\n- Update to git: 84f62e6f\n\n* Thu Feb 11 2016 jkonecny <jkonecny@redhat.com> - 0-158.20160210git050484eb\n- Add new build requires asciidoc\n- Use new man pages\n\n* Sat Mar 28 2015 jkonecny <jkonecny@redhat.com> - 0-5.20150328gitd1b81c8f\n- Automated git update by dgroc script new hash: d1b81c8f\n\n* Tue Mar 24 2015 Jiri Konecny <jkonecny@redhat.com> 0-1.7eaa697git\n- Add tests\n\n* Tue Mar 17 2015 Jiri Konecny <jkonecny@redhat.com> 0-1.12a732dgit\n- Create first rpm for kakoune\n"
  },
  {
    "path": "contrib/tmux-256color.terminfo",
    "content": "tmux-256color|tmux with 256 color and palette setting,\n\tam,\n\ths,\n\tkm,\n\tmir,\n\tmsgr,\n\txenl,\n\tcolors#256,\n\tcols#80,\n\tit#8,\n\tlines#24,\n\tpairs#32767,\n\tacsc=++\\,\\,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,\n\tbel=^G,\n\tblink=\\E[5m,\n\tbold=\\E[1m,\n\tcbt=\\E[Z,\n\tccc,\n\tcivis=\\E[?25l,\n\tclear=\\E[H\\E[J,\n\tcnorm=\\E[34h\\E[?25h,\n\tcr=^M,\n\tcsr=\\E[%i%p1%d;%p2%dr,\n\tcub=\\E[%p1%dD,\n\tcub1=^H,\n\tcud=\\E[%p1%dB,\n\tcud1=^J,\n\tcuf=\\E[%p1%dC,\n\tcuf1=\\E[C,\n\tcup=\\E[%i%p1%d;%p2%dH,\n\tcuu=\\E[%p1%dA,\n\tcuu1=\\EM,\n\tcvvis=\\E[34l,\n\tdch=\\E[%p1%dP,\n\tdch1=\\E[P,\n\tdim=\\E[2m,\n\tdl=\\E[%p1%dM,\n\tdl1=\\E[M,\n\tdsl=\\E]0;\\007,\n\ted=\\E[J,\n\tel=\\E[K,\n\tel1=\\E[1K,\n\tenacs=\\E(B\\E)0,\n\tflash=\\Eg,\n\tfsl=^G,\n\thome=\\E[H,\n\thpa=\\E[%i%p1%dG,\n\tht=^I,\n\thts=\\EH,\n\tich=\\E[%p1%d@,\n\til=\\E[%p1%dL,\n\til1=\\E[L,\n\tind=\\n,\n\tindn=\\E[%p1%dS,\n\tinitc=\\E]4;%p1%d;rgb\\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\\E\\\\,\n\tinvis=\\E[8m,\n\tis2=\\E)0,\n\tkDC=\\E[3;2~,\n\tkEND=\\E[1;2F,\n\tkHOM=\\E[1;2H,\n\tkIC=\\E[2;2~,\n\tkLFT=\\E[1;2D,\n\tkNXT=\\E[6;2~,\n\tkPRV=\\E[5;2~,\n\tkRIT=\\E[1;2C,\n\tkbs=^H,\n\tkcbt=\\E[Z,\n\tkcub1=\\EOD,\n\tkcud1=\\EOB,\n\tkcuf1=\\EOC,\n\tkcuu1=\\EOA,\n\tkdch1=\\E[3~,\n\tkend=\\E[4~,\n\tkf1=\\EOP,\n\tkf10=\\E[21~,\n\tkf11=\\E[23~,\n\tkf12=\\E[24~,\n\tkf13=\\E[1;2P,\n\tkf14=\\E[1;2Q,\n\tkf15=\\E[1;2R,\n\tkf16=\\E[1;2S,\n\tkf17=\\E[15;2~,\n\tkf18=\\E[17;2~,\n\tkf19=\\E[18;2~,\n\tkf2=\\EOQ,\n\tkf20=\\E[19;2~,\n\tkf21=\\E[20;2~,\n\tkf22=\\E[21;2~,\n\tkf23=\\E[23;2~,\n\tkf24=\\E[24;2~,\n\tkf25=\\E[1;5P,\n\tkf26=\\E[1;5Q,\n\tkf27=\\E[1;5R,\n\tkf28=\\E[1;5S,\n\tkf29=\\E[15;5~,\n\tkf3=\\EOR,\n\tkf30=\\E[17;5~,\n\tkf31=\\E[18;5~,\n\tkf32=\\E[19;5~,\n\tkf33=\\E[20;5~,\n\tkf34=\\E[21;5~,\n\tkf35=\\E[23;5~,\n\tkf36=\\E[24;5~,\n\tkf37=\\E[1;6P,\n\tkf38=\\E[1;6Q,\n\tkf39=\\E[1;6R,\n\tkf4=\\EOS,\n\tkf40=\\E[1;6S,\n\tkf41=\\E[15;6~,\n\tkf42=\\E[17;6~,\n\tkf43=\\E[18;6~,\n\tkf44=\\E[19;6~,\n\tkf45=\\E[20;6~,\n\tkf46=\\E[21;6~,\n\tkf47=\\E[23;6~,\n\tkf48=\\E[24;6~,\n\tkf49=\\E[1;3P,\n\tkf5=\\E[15~,\n\tkf50=\\E[1;3Q,\n\tkf51=\\E[1;3R,\n\tkf52=\\E[1;3S,\n\tkf53=\\E[15;3~,\n\tkf54=\\E[17;3~,\n\tkf55=\\E[18;3~,\n\tkf56=\\E[19;3~,\n\tkf57=\\E[20;3~,\n\tkf58=\\E[21;3~,\n\tkf59=\\E[23;3~,\n\tkf6=\\E[17~,\n\tkf60=\\E[24;3~,\n\tkf61=\\E[1;4P,\n\tkf62=\\E[1;4Q,\n\tkf63=\\E[1;4R,\n\tkf7=\\E[18~,\n\tkf8=\\E[19~,\n\tkf9=\\E[20~,\n\tkhome=\\E[1~,\n\tkich1=\\E[2~,\n\tkind=\\E[1;2B,\n\tkmous=\\E[M,\n\tknp=\\E[6~,\n\tkpp=\\E[5~,\n\tkri=\\E[1;2A,\n\tnel=\\EE,\n\top=\\E[39;49m,\n\trc=\\E8,\n\trev=\\E[7m,\n\tri=\\EM,\n\tritm=\\E[23m,\n\trmacs=^O,\n\trmcup=\\E[?1049l,\n\trmir=\\E[4l,\n\trmkx=\\E[?1l\\E>,\n\trmso=\\E[27m,\n\trmul=\\E[24m,\n\trs2=\\Ec\\E[?1000l\\E[?25h,\n\tsc=\\E7,\n\tsetab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,\n\tsetaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,\n\tsgr=\\E[0%?%p6%t;1%;%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;            5%;%?%p5%t;2%;m%?%p9%t\\016%e\\017%;,\n\tsgr0=\\E[m\\017,\n\tsitm=\\E[3m,\n\tsmacs=^N,\n\tsmcup=\\E[?1049h,\n\tsmir=\\E[4h,\n\tsmkx=\\E[?1h\\E=,\n\tsmso=\\E[7m,\n\tsmul=\\E[4m,\n\ttbc=\\E[3g,\n\ttsl=\\E]0;,\n\tvpa=\\E[%i%p1%dd,\n"
  },
  {
    "path": "doc/autoedit.asciidoc",
    "content": "Edition auto insertion in Kakoune\n=================================\n\nIt is a quite common feature for a code editor to help the programmer\nby automatically inserting some text in certain contexts. This document\ngoal is to explain how this is done in Kakoune.\n\nThere is no special support in Kakoune for this task, hooks are\nexpected to be used in order to manage that, and the normal Kakoune\nediting command are expected to be expressive enough so that relatively\ncomplex indentation can be written concisely.\n\nThe main hook is *InsertChar*, which gets called immediately _after_ a\ncharacter has been inserted in insertion mode due to the user pressing\nthe corresponding key.\n\nPrevious line indentation\n-------------------------\n\nLet's see a simple indent hook: preserving the previous line indentation.\n\nHere is the Kakoune normal mode key list in order to do that: \n\n----------------------------------------------------------------\nk<a-x>      # 1. go to previous line and select it\ns^\\h+<ret>y # 2. select the leading spaces and copy them\nj<a-h>P     # 3. go back to next line start and paste the spaces\n----------------------------------------------------------------\n\nNote that if nothing gets selected on phase *2.*, an error will be raised.\n\nWe want to do that each time the user jumps a line, just after the new line\nis inserted. So the hook would be:\n\n--------------------------------------------------------\n:hook InsertChar \\n %{ exec k<a-x> s^\\h+<ret>y j<a-h>P }\n--------------------------------------------------------\n\n(exec concatenates the keys for all argument, so the spaces will be ignored,\nallowing for clearer separation. either use <space> or quote the argument to\nuse a space key)\n\nThat works, however if the phase *2.* raises an error, the +:exec+ will stop\nand the user will get its selections on the previous line. The solution\nis to use a *draft* context, instead of the one the user is interacting with.\n\n---------------------------------------------------------------\n:hook InsertChar \\n %{ exec -draft k<a-x> s^\\h+<ret>y j<a-h>P }\n---------------------------------------------------------------\n\nThat way, exec is executed in a *copy* of the user's context, which means it\nmanipulates a *copy* of the user's selections.\n\nIncreasing indentation\n----------------------\n\nA little bit more complicated is to increase indentation whenever we insert a \nnew line after a +{+ or a +(+.\n\nThe complexity arises from the presence of a condition. We want to increase \nthe indentation *only* if the previous line ends with +{+ or +(+.\n\nFortunately, Kakoune provides us with a command for that: the +<a-k>+ command,\nwhich keeps selections where a certain regex can be found.\n\nHere is how we can do that:\n\n-------------------------------------------------------------------------------\nk<a-x>             # 1. select the previous line\n<a-k>[{(]\\h*$<ret> # 2. keep selections that end with { or ( followed by blanks\nj<a-gt>            # 3. go back to next line and indent it even if it is empty\n-------------------------------------------------------------------------------\n\nNote that if no previous line ends with a +{+ or +(+, the +<a-k>+ command will\nraise an error, and stop the execution. This is what we want: it is similar to\nwhat would happen if we would continue with no selections; the following \ncommands would have no effects.\n\nHowever, the error would end up being caught by the hook execution code, and\nit will write information about it in the debug buffer, which we do not want,\nas this is actually expected. In order to prevent that, the exec should be\nwrapped in a try command. So we would have:\n\n-------------------------------------------------------------------------------\n:hook InsertChar \\n %[ try %[ exec -draft k<a-x> <a-k>[{(]\\h*$<ret> j<a-gt> ] ]\n-------------------------------------------------------------------------------\n\n"
  },
  {
    "path": "doc/coding-style.asciidoc",
    "content": "C++ Coding Style\n================\n\nKakoune is written in C++20, here are the main coding style points:\n\n * Avoid external dependencies besides posix/stdc++\n\n * 4 spaces for indentation, no tabs\n\n * public interface before private methods/data when defining a class\n\n * use +override+ keyword for overridden virtual methods\n\n * opening brackets on their own lines by default, except when declaring\n   an object where the opening bracket follows the equal sign.\n\n * use alternative logical operator names (and, or, not instead of &&, ||, !)\n\n-----\nint func()\n{\n    if (condition)\n    {\n        ...\n    }\n    else\n        statement;\n}\n\nint array[] = {\n...\n};\n-----\n\n * End lines with an operator when continuing on the next line\n\n----\nif (condition1 or\n    condition2)\n----\n\n * Try to keep under 80 columns, even though this is not a strict limit.\n\n * CamelCase for types, snake_case for variables/function names\n\n * prefix fields with m_, static ones with ms_ except for dumb structs\n   (struct with every field public) where these prefixes can be dropped.\n\n * use const and constexpr liberally\n"
  },
  {
    "path": "doc/design.asciidoc",
    "content": "Kakoune design\n==============\n\nThis document describes the design goals for Kakoune, including rationale.\n\nInteractivity\n-------------\n\nUnlike Vim, Kakoune does not have an underlying line-oriented editor. It is\nalways expected to be used in an interactive fashion, displaying edited text in\nreal time. That should not prevent Kakoune from being used non-interactively\n(executing a macro for example), but priority should be given to ease of\ninteractive use.\n\nLimited scope\n-------------\n\nKakoune is a code editor. It is not an IDE, not a file browser, not a word\nprocessor and not a window manager. It should be very efficient at editing code.\nAs a side effect, it should be very efficient at editing text in general.\n\nComposability\n-------------\n\nBeing limited in scope to code editing should not isolate Kakoune from its\nenvironment. On the contrary, Kakoune is expected to run on a Unix-like\nsystem alongside a lot of text-based tools, and should make it easy to\ninteract with these tools.\n\nFor example, sorting lines should be done using the Unix sort command, not\nwith an internal implementation. Kakoune should make it easy to do that,\nhence the +|+ command for piping selected text through a filter.\n\nThe modern Unix environment is not limited to text filters. Most people use\na graphical interface nowadays, and Kakoune should be able to take advantage\nof that without hindering text mode support. For example, Kakoune enables\nmultiple windows by supporting many clients on the same editing session,\nnot by reimplementing tiling and tabbing. Those responsibilities are left\nto the system window manager.\n\nOrthogonality\n-------------\n\nKakoune features should be as orthogonal as possible. For example, in Vim,\nthere are many ways to modify the buffer: Through normal/insert\nmode, command mode, and Vim scripts. In Kakoune, modifying the buffer is\nonly the job of normal/insert mode.\n\nThat means there should be clear separation of concerns between modes:\n\n * normal mode is for manipulating the selection and the selection contents.\n\n * insert mode is for interactive insertion into the buffer.\n\n * command mode is for non-editing features (opening a file, setting\n   options...).\n\nOrthogonality is an ideal; it should not forbid common sense pragmatism.\nThe +gf+ and +ga+ commands are not strictly selection manipulation commands,\nbut they do fit nicely with other +goto+ commands, so they are acceptable in\nnormal mode even though they could arguably be moved to command mode.\n\nModes should be orthogonal, as should commands within modes. For\nexample, Vim uses both +d+ and +x+ to delete text, with minor differences. In\nKakoune only +d+ exists, and the design ensures that +x+ is not needed.\n\nSpeed\n-----\n\nKakoune should be fast -- fast to use, as in a lot of editing in a few\nkeystrokes, and fast to execute.\n\n * Vim is the benchmark here. Most editing tasks should be doable in fewer\n   or the same number of keystrokes as Vim.\n\n * Kakoune is designed with asynchronicity in mind. Launching a background\n   process and using its result when available should not block the editor.\n\n * Kakoune should be implemented with speed in mind. A slow editor is a\n   useless one.\n\nSimplicity\n----------\n\nSimplicity is nice. Simplicity correlates with orthogonality and speed. It makes\nthings easier to understand, bugs easier to fix, and code easier to change.\n\n * *No threading*: multithreading is a hard problem and is not well suited\n   to a text editor:\n\n   - When we want a direct result, we need to be synchronous with\n     the user. A 4x speed improvement is meaningless; we need to have an\n     algorithm which appears instantaneous to the user.\n\n   - When we want an asynchronous result, the processing is best left\n     to a helper command which can be reused with other Unix tools.\n\n * *No binary plugins*: shared objects by themselves add a lot of\n   complexity. Plugins add another interface to Kakoune and go against\n   orthogonality. The +%sh{ ... }+ and socket interfaces should be made good\n   enough for most plugin use cases.\n  \n   - Rather than writing a plugin for intelligent code completion or source\n     code navigation, it is better to write an independent helper tool that can\n     interact with Kakoune through the shell.\n\n * *No integrated scripting language*: for the same reason as binary plugins.\n\n * *Limited smartness*: Kakoune should not try to be too smart. Being smart\n   is often unpredictable for the user and makes things context-dependent.\n   When Kakoune tries to be smart, it should provide the alternative,\n   'non-smart' version. For instance, +\\*+ tries to detect word boundaries on\n   the selection, but +alt-*+ opts out of this behavior.\n\nUnified interactive use and scripting\n-------------------------------------\n\nAs an effect of both Orthogonality and Simplicity, normal mode is not\na layer of keys bound to a text editing language layer. Normal mode *is*\nthe text editing language.\n\nThat means there is no +delete-selected-text+ command that +d+ is bound\nto. +d+ *is* the +delete selected text+ command.\n\nThis permits both scripting and interactive use cases to share the same text\nediting language. Both use normal mode to express complex editing.\n\nBesides promoting simplicity by avoiding the introduction of another\nlayer, this helps ensure the interactive editing language is expressive\nenough to handle complex use cases, such as indentation hooks.\n\nLanguage-agnostic\n-----------------\n\nKakoune should not be tailored for writing in a specific programming\nlanguage. Support for different languages should be provided by a kak script\nfile. Built-in language support should be avoided.\n\nSelf-documenting\n----------------\n\nKakoune should be able to document its features. Live documentation, along\nwith an extensive suggestion/completion system, provides the discoverability\nwhich is often lacking in non-GUI tools. As much as possible, documentation\nshould be integrated with the code so that it stays up to date.\n\nVim compatibility\n-----------------\n\nKakoune is inspired by Vim and should try to keep its commands similar to\nVim's if there are no compelling reasons to deviate. However, self-consistency\nis more important than Vim compatibility.\n"
  },
  {
    "path": "doc/interfacing.asciidoc",
    "content": "Interfacing Kakoune with external programs\n==========================================\n\nIn order to interact with the external world, Kakoune uses the shell, mainly\nthrough the +%sh{ ... }+ string type, and its control socket.\n\nBasic interaction\n-----------------\n\nFor synchronous operations, +%sh{ ... }+ blocks are easy to use, they behave\nsimilarly to +$( ... )+ shell construct.\n\nFor example, one can echo the current time in Kakoune's status line using:\n\n[source,bash]\n----\n:echo %sh{ date }\n----\n\nFor asynchronous operations, the Kakoune Unix stream socket can be used. This\nis the same socket that Kakoune clients connect to. It is available through the\n+kak_session+ environment variable: the socket is\n+/tmp/kakoune/${username}/${kak_session}+\n\nFor example, we can echo a message in Kakoune in 10 seconds with:\n\n[source,bash]\n----\n:nop %sh{ {\n    sleep 10\n    echo \"eval -client '$kak_client' 'echo sleep ended'\" |\n        kak -p ${kak_session}\n} > /dev/null 2>&1 < /dev/null & }\n----\n\n * The +nop+ command is used so that any eventual output from the\n   +%sh{ ... }+ is not interpreted by Kakoune\n * When writing to the socket, Kakoune has no way to guess in which\n   client's context the command should be evaluated. A temporary\n   context is used, which does not have any user interface, so if we want\n   to interact with the user, we need to use the +eval+ command, with\n   its +-client+ option to send commands to a specific client.\n * For the command to run asynchronously, we wrap it in a sub shell\n   with braces, redirect its +std{in,err,out}+ to +/dev/null+, and\n   run it in background with +&+. Using this pattern, the shell does\n   not wait for this sub shell to finish before quitting.\n\nInteractive output\n------------------\n\nIt is a frequent interaction mode to run a program and display its output\nin a Kakoune buffer.\n\nThe common pattern to do that is to use a fifo buffer:\n\n[source,bash]\n-----\nevaluate-commands %sh{\n     # Create a temporary fifo for communication\n     output=$(mktemp -d -t kak-temp-XXXXXXXX)/fifo\n     mkfifo ${output}\n     # run command detached from the shell\n     { run command here > ${output} } > /dev/null 2>&1 < /dev/null &\n     # Open the file in Kakoune and add a hook to remove the fifo\n     echo \"edit! -fifo ${output} *buffer-name*\n           hook buffer BufClose .* %{ nop %sh{ rm -r $(dirname ${output})} }\"\n}\n-----\n\nThis is a very simple example, most of the time, the echo command will as\nwell contain\n\n-----\nset buffer filetype <...>\n-----\n\nand some hooks for this filetype will have been written\n\nCompletion candidates\n---------------------\n\nFiletype specific completion should be provided by external programs.\n\nExternal completions are provided using an option to store completion, which\nhave the following format.\n\n----\nline.column[+len]@timestamp candidate1|select1|menu1 candidate2|select2|menu2 ...\n----\n\nthe first element of this string list specify where and when this completion\napplies, the others are a triplet `<completion text>|<select cmd>|<menu text>`\n\nThe select command is executed whenever this menu item gets selected, and\nis usually used to display an item specific documentation with\n`info -placement menu '<menu item description>'`\n\nThe menu text is a markup string (see <<faces#markup-strings,`:doc faces\nmarkup-strings`>>), so it can contain `{face}` directives.\n\nTo effectively use that completion option, it should get added to the completers\noption.\n\n---\nset -add buffer completers option=my_option_name\n---\n\nAs a completion program may take some time to compute the candidates, it should\nrun asynchronously. In order to do that, the following pattern may be used:\n\n[source,bash]\n-----\n# Declare the option which will store the temporary filename\ndecl str plugin_filename\n# Declare the completion option\ndecl completions plugin_completions\n# Add plugin_completions to completers for files of good filetype\nhook global BufSetOption filetype=my_filetype %{\n    set -add buffer completers option=plugin_completions\n}\nevaluate-commands %sh{\n    # ask Kakoune to write current buffer to temporary file\n    filename=$(mktemp -t kak-temp.XXXXXXXX)\n    echo \"set buffer plugin_filename '$filename'\n          write '$filename'\"\n}\n# End the %sh{} so that its output gets executed by Kakoune.\n# Use a nop so that any eventual output of this %sh does not get interpreted.\nnop %sh{ { # launch a detached shell\n    buffer=\"${kak_opt_plugin_filename}\"\n    line=\"${kak_cursor_line}\"\n    column=\"${kak_cursor_column}\"\n    # run completer program and format the output in a list of completions\n    candidates=$(completer $buffer $line $column | completer_filter)\n    # remove temporary file\n    rm $buffer\n    # generate completion option value\n    completions=\"$line.$column@$kak_timestamp $candidates\"\n    # write to Kakoune socket for the buffer that triggered the completion\n    echo \"set buffer=${kak_bufname} plugin_completions $completions\" |\n        kak -p ${kak_session}\n} > /dev/null 2>&1 < /dev/null & }\n-----\n"
  },
  {
    "path": "doc/json_ui.asciidoc",
    "content": "Json-Rpc user interface\n=======================\n\nKakoune user interfaces can be implemented through the\nhttps://www.jsonrpc.org/specification[json-rpc 2.0 protocol].\n\nBy launching Kakoune in client mode with the `-ui json` option, the launched\nclient will write newline separated json-rpc requests on stdout and read\njson-rpc requests on stdin.  Errors will be reported on stderr (not in\njson format).\n\nKakoune requests are always using positional parameters, never named, and\nKakoune won't be able to parse named parameters in requests.\n\nHere are the data structures used:\n\n* Color: a string, either a named color, or #rrggbb, or 'default'\n* Attribute: one of {underline, curly_underline, double_underline, reverse, blink, bold, dim, italic, final_fg, final_bg, final_attr}\n* Face { Color fg; Color bg; Array<Attribute> attributes; }\n* Atom { Face face; String contents; }\n* Line : Array of Atom\n* Coord { int line; int column }\n\nHere are the requests that can be written by the json ui on stdout:\n\n* draw(Array<Line> lines, Coord cursor_pos, Face default_face, Face padding_face, int widget_columns)\n  padding_face is the face of the padding characters '~' in the\n  terminal UI.\n  cursor_pos is the main cursor coordinates\n  widget_columns is the number of columns at the start of each line used by flag-lines highlighters.\n* draw_status(Line prompt, Line content, int cursor_pos, Line mode_line, Face default_face)\n  cursor_pos is the location of the cursor in the content, the UI is expected to trim\n  content in order to ensure the cursor stays visible, if cursor_pos is negative, no\n  cursor should be displayed in the prompt. Prompt cursor show have priority to buffer\n  cursor if only one cursor can be displayed.\n* menu_show(Array<Line> items, Coord anchor, Face selected_item_face, Face menu_face,\n            String style)\n  style can be:\n  - prompt: display the menu as a prompt menu (anchor is ignored)\n  - inline: display the menu next to (above or below) the anchor coordinate\n  - search: display the menu as a search menu (anchor is ignored)\n* menu_select(int selected)\n* menu_hide()\n* info_show(Line title, Array<Line> content, Coord anchor, Face face, String style)\n  style can be:\n  - prompt: display the info as a prompt info (anchor is ignored)\n  - inline: display the info next to (above or below) the anchor coordinate\n  - inlineAbove: display the info next to (above) the anchor coordinate\n  - inlineBelow: display the info next to (below) the anchor coordinate\n  - menuDoc: display the info next to the menu, as a documentation for it\n  - modal: display the info in a way that shows Kakoune is waiting for a\n    special action related to it.\n* info_hide()\n* set_ui_options(Map<String, String> options)\n  called when ui_options changed with a map of options name to option values\n* refresh(bool force)\n\nThe requests that the json ui can interpret on stdin are:\n\n* keys(String key1, String key2...): keystrokes\n* resize(int rows, int columns): notify ui resize\n* scroll(int amount, int line, int colum): scroll by given line amount, line and\n  column relate to cursor position\n* mouse_move(int line, int column): line and column relate to the cursor position.\n* mouse_press(String button, int line, int column): line and column relate to\n  cursor position, button can be 'left', 'middle' or 'right'\n* mouse_release(String button, int line, int column): same.\n* menu_select(int index): explicit select of given menu entry\n"
  },
  {
    "path": "doc/kak.1",
    "content": ".Dd June 15, 2021\n.Dt KAK 1\n.Os\n.Sh NAME\n.Nm kak\n.Nd a vim-inspired, selection oriented code editor\n.\n.Sh SYNOPSIS\n.Nm\n.Op Fl d\n.Op Fl n\n.Op Fl ro\n.Op Fl c Ar session_id | Fl s Ar session_id\n.Op Fl ui Ar ui_type\n.Op Fl e Ar command\n.Op Fl E Ar command\n.Op Sy + Ns Ar line Ns Oo Sy \\&: Ns Ar column Oc | Sy +:\n.Op Ar file ...\n.\n.Nm\n.Fl f Ar keys\n.Op Fl q\n.Op Fl i Ar suffix\n.Op Ar file ...\n.\n.Nm\n.Fl p Ar session_id\n.\n.Nm\n.Fl l\n.Nm\n.Fl clear\n.\n.Nm\n.Fl version\n.\n.Nm\n.Fl help\n.\n.Sh DESCRIPTION\n.Sy Kakoune\nis a code editor heavily inspired by\n.Xr vim 1\nand\n.Xr vi 1 .\nAs such, most of its commands are similar to\n.Xr vi 1 Ap s,\nsharing its\n.Dq keystrokes as a text editing language\nmodel.\n.Pp\n.Sy Kakoune\noperates in two modes, normal and insertion.\nIn insertion mode, keys are directly inserted into the current buffer.\nIn normal mode, keys are used to manipulate the current selection and to\nenter insertion mode.\n.Pp\n.Sy Kakoune\nhas a strong focus on interactivity.\nMost commands provide immediate and incremental results, while still\nbeing competitive in keystroke count with vim.\n.Pp\n.Sy Kakoune\nworks on selections, which are oriented, inclusive ranges of characters.\nSelections have an anchor and a cursor.\nMost commands move both of them except when extending selection, where\nthe anchor character stays fixed and the cursor moves around.\n.Pp\nThe options are as follows:\n.Bl -tag -width indent\n.It Fl help\nDisplay a help message and quit.\n.\n.It Fl version\nDisplay Kakoune version and quit.\n.\n.It Fl n\nDo not load the system's kakrc.\n.Pq often, Pa /usr/share/kak/kakrc\n.\n.It Fl l\nList existing sessions.\n.\n.It Fl d\nRun as a headless session\n.Pq requires Fl s .\n.\n.It Fl e Ar command\nExecute\n.Ar command\nafter the client initialization phase.\n.\n.It Fl E Ar command\nExecute\n.Ar command\nafter the server initialization phase.\n.\n.It Fl f Ar keys\nRun Kakoune in\n.Sq filter mode ,\nlike\n.Xr sed 1 .\nFor standard input and each file named on the command line,\nthe whole buffer is selected\n(as with\n.Li % )\nthen\n.Ar keys\nare executed.\nThe filtered version of standard input\nis written to standard output.\nAny files mentioned on the command line\nare modified in-place,\nbut if\n.Fl i\nis provided then Kakoune makes a backup of the original version.\n.\n.It Fl i Ar suffix\nBackup the files on which a filter is applied, using the given suffix.\n.\n.It Fl q\nWhen in\n.Sq filter mode ,\ndon't print any errors\n.\n.It Fl p Ar session_id\nSend the commands written on the standard input to session\n.Ar session_id .\n.\n.It Fl c Ar session_id\nConnect to the given session\n.Ar session_id .\n.\n.It Fl s Ar session_id\nSet the current session name to\n.Ar session_id .\n.\n.It Fl ui Ar type\nSelect the user interface type, which can be\n.Em terminal ,\n.Em dummy ,\nor\n.Em json .\n.\n.It Fl clear\nRemove sessions that were terminated in an incorrect state\n.Pq e.g. after a crash .\n.\n.It Fl ro\nBegin in\n.Em readonly mode ,\nall the buffers opened will not be written to disk.\n.\n.It Sy + Ns Ar line Ns Oo Sy \\&: Ns Ar column Oc | Sy +:\nSpecify a target line and column for the first file.\nWhen the plus sign is followed by only a colon, then the cursor is sent\nto the last line of the file.\n.\n.It Ar file ...\nOne or more\n.Ar file(s)\nto edit.\n.El\n.\n.Sh ENVIRONMENT\n.Bl -tag -width 6n\n.It Ev KAKOUNE_POSIX_SHELL\nOverrides the POSIX shell used for\n.Em %sh{...}\nexpansion, which is\n.Pa /bin/sh\n.Pq Xr sh 1\nif unset.\n.\n.It Ev KAKOUNE_CONFIG_DIR\nOverrides the location of the directory containing Kakoune user\nconfiguration.\nIf unset,\n.Pa $XDG_CONFIG_HOME/kak\nis used.\n.\n.It Ev KAKOUNE_RUNTIME\nOverrides the location of the directory containing the Kakoune support files.\nIf unset, location is determined from Kakoune's binary location.\n.\n.It Ev XDG_CONFIG_HOME\nPath to the user's configuration directory.\nIf unset,\n.Pa $HOME/.config\nis used.\n.\n.It Ev XDG_RUNTIME_DIR\nPath to the user's session's sockets.\nIf unset,\n.Pa $TMPDIR/kakoune\nis used.\n.El\n.\n.Sh FILES\nIn the paths documented below,\n.Pa <rtdir>\nrefers to the runtime directory, whose value is determined in relation\nto the path to\n.Nm Ap s\nbinary location if \n.Pa $KAKOUNE_RUNTIME\nis not set:\n.Pa <rtdir>\n=\n.Pa <path_to_kak_binary>/../share/kak .\n.\n.Pp\nIf not started with the\n.Fl n\nswitch,\n.Nm\nwill first load\n.Pa <rtdir>/kakrc ,\nwhich will in turn load the following additional files:\n.\n.Bl -bullet\n.It\nIf the\n.Pa $KAKOUNE_CONFIG_DIR/autoload\ndirectory exists, recursively load every\n.Pa *.kak\nfile in it, and its sub-directories.\n.\n.It\nIf it does not exist, fall back to the system-wide autoload directory in\n.Pa <rtdir>/autoload ,\nand recursively load all files in a similar way.\n.\n.It\n.Pa <rtdir>/kakrc.local ,\nif it exists; this is a user-defined system-wide configuration.\n.\n.It\n.Pa $KAKOUNE_CONFIG_DIR/kakrc ,\nif it exists; this is the user configuration.\n.\n.El\n.\n.Pp\nConsequently, if the\n.Pa $KAKOUNE_CONFIG_DIR/autoload\ndirectory exists, only scripts stored within that directory will be\nloaded \\(em the built-in\n.Pa *.kak\nfiles will not be.\n.\n.Pp\nUsers who still want the built-in scripts to be loaded along their own\ncan create a symbolic link to\n.Pa <rtdir>/autoload\n.Pq or to individual scripts in it\nin their user-configuration directory:\n.\n.Pp\n.Dl ln -s \\fI<rtdir>\\fR/autoload \"${XDG_CONFIG_HOME:-$HOME/.config}\"/kak/autoload\n.\n.Sh EXAMPLES\n.Bl -tag -width 6n\n.It Edit a file:\n.Nm\n.Pa /path/to/file\n.\n.It Edit multiple files (multiple buffers will be created):\n.Nm\n.Pa ./file1.txt\n.Pa /path/to/file2.c\n.\n.It Prepend a modeline that sets the tabstop to multiple files:\n.Nm\n.Fl f Qq ggO// kak: tabstop=8<esc>\n.Pa *.c\n.El\n.\n.Sh SEE ALSO\nFor the complete on-line documentation, use the\n.Ic :doc\ncommand after starting\n.Nm .\n.\n.Pp\n.Lk https://github.com/mawww/kakoune/wiki The Kakoune wiki .\n.Pp\n.Lk https://kakoune.org The main Kakoune web site .\n.Pp\n.Xr vi 1 ,\n.Xr vim 1 ,\n.Xr sam 1plan9 .\n.\n.Sh AUTHORS\n.An Maxime Coste Aq Mt frrrwww@gmail.com\nand many others.\n"
  },
  {
    "path": "doc/pages/buffers.asciidoc",
    "content": "= Buffers\n\n== Commands\n\nTo open buffers or navigate through the buffers list see\n<<commands#files-and-buffers,`:doc commands files-and-buffers`>>.\n\n== Scratch Buffers\n\nScratch buffers are useful for volatile data and quick prototyping.\nThey are not linked to files, so Kakoune does not warn about unsaved\nchanges at exit, and the `:write` command requires an explicit filename.\n\nOne particular scratch buffer, named `\\*scratch*`, is automatically created\nwhen there are no other buffers left in the current session, which is also\nthe case when Kakoune starts up without any files to open.\n\nA scratch buffer can be created by passing the `-scratch` switch to the\n`:edit` command.\n\n== Debug Buffers\n\nDebug buffers are used to gather diagnostics. They have a number of\nrestrictions compared to regular buffers:\n\n- They are skipped when cycling over the buffers list.\n- Their content is not considered for word completions with `word=all`\n  completers.\n- Hooks are not always run (like the `BufCreate`/`BufClose` hooks).\n- Display profiling is disabled.\n\nA specific `\\*debug*` buffer is used by Kakoune to write errors or\nwarnings.  This is also where the ouput of the `:debug` and the `:echo\n-debug` commands will land.\n\nA debug buffer can be created by passing the `-debug` switch to the\n`:edit` command.\n\n== FIFO Buffers\n\nThe `:edit` command can take a `-fifo` switch:\n\n---------------------------------------------\n:edit -fifo <filename> [-scroll] <buffername>\n---------------------------------------------\n\nIn this case, a buffer named `<buffername>` is created which reads\nits content from the FIFO (also called \"named pipe\") `<filename>`.\nWhen the FIFO is written to, the buffer is automatically updated.\n\nIf the `-scroll` switch is specified, the window displaying the buffer\nwill scroll so that the newest data is always visible.\n\nThis is very useful for running some commands asynchronously while\ndisplaying their result in a buffer. See `rc/make.kak` and `rc/grep.kak`\nfor examples.\n\nWhen the write end of the FIFO is closed, the buffer becomes an ordinary\n<<buffers#scratch-buffers,scratch buffer>>. When the buffer is deleted,\nKakoune closes the read end of the FIFO. Any program writing to the FIFO\nwill receive `SIGPIPE`, which will terminate the program by default.\n"
  },
  {
    "path": "doc/pages/changelog.asciidoc",
    "content": "= Changelog\n\nTo control the startup info message, see <<options#startup-info,`:doc options startup-info`>>\n\nThis changelog contains major and/or breaking changes to Kakoune between\nreleased versions.\n\n== Development Version\n\n* `finaleol` option to support writing files that do not end with an final\n  end-of-line byte\n\n* `FocusIn`/`FocusOut` events on suspend\n\n* `%val{buffile}` is now empty for scratch buffers\n\n* Reworked Json UI draw_status call to give UI implementation more control,\n  added cursor pos to the draw call and removed the set_cursor call\n\n* `number-lines -full-relative` switch to keep a smaller line number gutter.\n\n* `<a-I>` and `<a-A>` to select nested text objects\n\n* `kak -C <session>` connects to a session or creates it if it does not exist.\n\n== Kakoune 2025.06.03\n\n* Expose env vars that are mentioned in the arguments passed to shell expansions\n\n* Support for colored double underlines\n\n* `git apply` can now operate on selected changes in the current buffer's\n  file (useful for quick (un)staging and reverting)\n\n* `exec/eval -client` switch accepts '*' for all clients and comma separated\n  list of client names.\n\n== Kakoune 2024.05.18\n\n* Fixed tests on Alpine Linux and *BSD\n\n== Kakoune 2024.05.09\n\n* `flag-lines -after` switch to display text after the line\n\n* `shell-script-candidates` completion now runs the script asynchronously\n  while displaying and updating results live.\n\n* `%val{window_range}` elements are now emitted as different strings\n\n* `+` only duplicates identical selections a single time to avoid surprising\n  and slow exponential growth in the number of selections.\n\n* `daemonize-session` command makes it possible to convert the current session\n  to a daemon one (which will not exit on last client disconnecting)\n\n* View mode commands and mouse scrolling no longer change selections when those go off-screen.\n\n* New commands `git apply`, `git blame-jump`, `git edit` and `git grep`.\n\n* `git blame` now also works in `git-diff` and `git-log` buffers.\n\n* Completions provided via `shell-script-candidates` or `completers` are no longer sorted if the typed text is empty.\n\n* The `terminal` alias has been replaced with a command that selects terminal program and placement based on windowing options.\n\n* `local` scopes in `commands` and `evaluate-commands`.\n\n== Kakoune 2023.08.08\n\n* Fix compilation errors on FreeBSD and MacOS using clang\n\n== Kakoune 2023.07.29\n\n* `<a-u>` and `<a-U>` now undo/redo selection changes; the previous meaning\n  of moving in history tree has been moved to `<c-j>` and `<c-k>`\n\n* `%exp{...}` expansions provide flexible quoting for expanded strings\n  (as double quoted strings)\n\n* `<c-g>` cancels the current operation and goes back to the main event\n  loop, this provides an escape hatch when Kakoune seems to hang due to\n  a costly operation\n\n* `show-matching -previous` highlighter will fall back onto the character\n  preceeding the cursor\n\n== Kakoune 2022.10.31\n\n* `complete-command` (See <<commands#configuring-command-completion,`:doc commands configuring-command-completion`>>)\n\n* `p`, `P`, `!` and `<a-!>` commands now select the inserted text\n\n* `x` now just extends the selection to contain full lines (as `<a-x>` did)\n  `<a-x>` trims partial lines from the selection (as `<a-X>` did)\n\n* User mappings is now bound to `<space>` while keeping/removing main selection\n  moved to `,` and `<a-,>`\n\n* Prompt history registers `%reg{colon}`, `%reg{slash}` and `%reg{pipe}` now\n  have reverse chronological order\n\n* Executing user mode mappings no longer adds to prompt history registers.\n\n== Kakoune 2021.11.07\n\n* Support for curly and separately colored underlines (undocumented in 2021.10.28)\n\n* Fixes for terminal flickering\n\n* Fixes for command and response fifo corner cases\n\n== Kakoune 2021.10.28\n\n* `g` and `v` do not auto lower case the next key, so `GL` needs to be\n  manually mapped to `Gl` for example.\n\n== Kakoune 2021.08.28\n\n* command and response fifo support\n  (See <<expansions#command-and-response-fifo,`:doc expansions command-and-response-fifo`>>)\n\n* Shell expansions only trim the last trailing newline instead of all of\n  them to make is possible to losslessly pass text through `%sh{...}`. \n\n* `set-option -remove` support for subtracting/removing from option values\n\n* Menu completions such as command name completion are now auto-inserted on\n  space\n\n* `write -atomic` was replaced with `write -method [replace|overwrite]` to\n  make both write methods available explicitly\n\n* `write <filename>` will fail if the given filename already exists and is\n  a regular file. Use the `-force` switch to override that behaviour.\n\n== Kakoune 2020.09.01\n\n* The `repl` and `send-text` aliases have been renamed respectively into\n  `repl-new` and `repl-send-text`.\n\n* Daemon mode (`-d` switch) does not fork anymore.\n\n* Replace NCursesUI with a custom terminal UI implementation\n\n== Kakoune 2020.08.04\n\n* Introduce `User` hook support.\n\n* The `bold` and `italic` faces are no longer built-in. Highlighters\n  are expected to use face attributes (`+b` and `+i`, respectively) to\n  decorate text.\n\n* The `lint-enable` command no longer needs to be called to display\n  linting errors. The `lint-disable` command was renamed into\n  `lint-hide-diagnostics`.\n\n* The `+<length>` part of a `range-specs` highlighter consistently\n  refers to the length of the target range.\n\n* clients stdin is transferred to the server, making it possible\n  to pipe into `kak -c <session>`\n\n* Faces can have an alpha channel, specified using the \n  `rgba:RRGGBBAA` format.\n\n* replace-ranges highlighter now support empty and multi-lines ranges\n\n* `%val{...}` now expands to list of strings, `$kak_quoted_...` now work\n  as expected with these.\n\n* `*SetOption` hooks filter string will contain a value only for options\n  of int/str/bool types to avoid performance issue with generating those\n  on more complex option types. \n\n== Kakoune 2020.01.16\n\n* Expose history tree through `$kak_history` and\n  `$kak_uncommitted_modifications`\n\n* `InsertCompletionHide` parameter is the list of inserted\n  ranges\n\n== Kakoune 2019.12.10\n\n* Arrow keys and `<home>`, `<end>` are not normal mode commands\n  anymore but default key mappings.\n  \n* `ModeChange` hook parameter now takes `push:` or `pop:` prefix,\n  `InsertBegin`, `InsertEnd`, `NormalBegin` and `NormalEnd`\n  were removed.\n\n* `-verbatim` switch in `evaluate-commands` for perfect command\n  forwarding to another context.\n\n* `WrapMarker` face used by `wrap -marker` highlighter\n\n* `info` supports markup with the `-markup` switch\n\n* `rename-buffer` gained `-file` and `-scratch` switches\n  to support converting buffer types.  \n\n== Kakoune 2019.07.01\n\n* Re-organized bundled script files directory hierarchy.\n\n* Introduced helpers to write/read from file in scripts with\n  `%file{...}` expansion and `echo -to-file <filename>`.\n\n* Added `ClientCreate` and `ClientClose` hooks\n\n* `edit -scratch` with no buffer name will create a new\n  scratch buffer with a unique autogenerated name.\n\n* `info -placement` is now `info -style` and supports\n  `menu` and `modal` additional styles.\n\n* `completions` option type `docstring` are now arbitrary\n  kakoune commands that are run on item select.\n\n* `InsertCompletionSelect` hook has been removed as\n  `completions` commands now provides a similar feature.\n\n* Introduced a module system using the `provide-module` and\n  `require-module` commands that allows for lazily loading language\n  support files with dependency resolution.\n\n* Added a new hook `ModuleLoaded` which is run after a module is\n  loaded, allowing for module specific configuration.\n\n* Shell quoting of lists is not automatic anymore, `$kak_quoted_...`\n  makes it opt-in, and works for all option types.\n\n* Lower case function key syntax is not accepted anymore,\n  `<f1>` should be converted to `<F1>`.\n\n== Kakoune 2019.01.20\n\n* `auto_complete` has been renamed to `autocomplete` for more\n  consistency.\n\n* Start of a builtin key parser in the ncurses ui bypassing\n  the ncurses one. Can be favored by setting the ui option\n  `ncurses_builtin_key_parser` to `true`.\n\n* Right clicks extend the current selection, the control modifier allows\n  merging all the selections after extension.\n\n* The `regex` highlighter now supports named capture groups to\n  ease readability.\n\n== Kakoune 2018.10.27\n\n* `remove-hooks` <group> argument is now a regex and removes all\n  hooks whose group matches it.\n\n* `exclusive` face attribute (e) has been replaced with more\n  granular `final foreground` (f), `final background` (g), and `final\n  attributes` (a), or the three combined as `final` (F).  Semantics\n  changed slightly as those attributes apply to the existing face as\n  well (a final face will not get modified by a following face if that\n  following face does not have the final attribute itself.\n\n* `<a-m>` aka \"merge consecutive selections\" has been moved to `<a-_>`.\n  The new `<a-m>` and `<a-M>` are now symmetrical with `m` and `M`.\n  Those commands select (or extend) to the matching char backwards.\n\n* `define-command` switches `-shell-completion` and `-shell-candidates`\n  have been renamed to `-shell-script-completion` and\n  `-shell-script-candidates` to make way for a new `-shell-completion`\n  which completes like the shell (shell command name then filename).\n\n* `asciidoc` is not a dependency anymore, the last file that required\n  it (Kakoune's manpage) has been converted to troff format.\n\n== Kakoune 2018.09.04\n\nThis version contains a significant overhaul of various Kakoune\nfeatures that can break user configuration. This was a necessary\nchange to make Kakoune command model cleaner and more robust.\n\n* `%sh{...}` strings are not reparsed automatically anymore, they need\n  to go through an explicit `evaluate-commands`\n\n* The `-allow-override` switch from `define-command` has been renamed\n  `-override`.\n\n* The search prompt uses buffer word completion so that fuzzy completion\n  can be used to quickly search for a buffer word.\n\n* The `wrap` highlighter can accept a new `-marker <marker_text>` switch.\n\n* The command line syntax has changed to support robust escaping.\n\n  - `%sh{...}` is not expanded to multiple tokens automatically anymore,\n    to evaluate its output as multiple tokens/commands, use the\n    `evaluate-commands` command:\n\n   -------------------------------------------------------------\n   evaluate-commands %sh{ echo \"first command; second command\" }\n   -------------------------------------------------------------\n\n  - Escaping of `'` in `'...'` and `\"` and `%` in `\"...\"` strings is done\n    by doubling up (`''`, `\"\"` and `%%`) instead of using a backslash\n\n  - Bare words escaping has been tweaked.\n\n  See <<command-parsing#,`:doc command-parsing`>>.\n\n* Various lists (options, registers...) in Kakoune are now written using\n  the command line syntax:\n\n  - `set-register` now take an arbitrary number of parameters and sets\n    the register to multiple strings. `%reg` expands to a list of strings.\n\n  - the `$kak_reg_*` environment variable is now a list, `$kak_main_reg_*`\n    provides the previous behaviour.\n\n  - `%opt` expands list options as list of strings.\n\n  - selection descs are whitespaces separated instead of `:` separated\n\n* Highlighters syntax has changed to permit explicit naming and remove\n  highlighter specific name parameters (such as for the group highlighter)\n  `add-highlighter <path>/<name> <type> <params>` is the new syntax.\n\n* Regions highlighters have been overhauled and are now specified with\n  a sequence of commands instead of a single one:\n\n  ------------------------------------------------------------------\n  add-highlighter <path>/<name> regions\n  add-highlighter <path>/<name>/<region name> region <begin> <end> \\\n  <type> <params>\n  ------------------------------------------------------------------\n\n  The recursion regex is opt-in through a `-recurse <recurse>` flag.\n\n  They also are not necessarily groups anymore, a region can directly\n  apply any other highlighter\n\n  See <<highlighters#,`:doc highlighters`>>\n\n* Highlighter type names have been unified, types that used `_` as\n  word separators, such as `show_whitespaces` are now using `-`\n  (`show-whitespace`).\n\n* `a` on end of line is not treated specially anymore, it will start\n  inserting on the next character, which will be the first character\n  of the next line.\n\n* `autoshowcompl` options has been renamed `auto_complete` and is\n  now a `flags(insert|prompt)` option, allowing more granular\n  configuration of when the completions should be displayed\n  automatically.\n\n* Prompt editing shortcuts have been changed to match readline.\n\n== Kakoune 2018.04.13\n\nFirst official Kakoune release.\n"
  },
  {
    "path": "doc/pages/command-parsing.asciidoc",
    "content": "= Command Parsing\n\nKakoune commands, either loaded from a script or written in the command\nprompt, are parsed according to the following rules:\n\n== Basic parsing\n\n- Commands are terminated by a `;` or an end of line.\n\n- Words (command names and parameters) are delimited by whitespaces.\n\n== Quoted Strings\n\nIf a word starts with `'`, `\"`, or `%X` with `X` a _non-nestable_ punctuation\ncharacter (see <<command-parsing#balanced-strings,Balanced Strings>> below for\nnestable characters), it is parsed as a quoted string whose delimiter is,\nrespectively, `'`, `\"`, or `X`.\n\nA quoted string contains every character (including whitespaces).  Doubling\na closing delimiter escapes it.  Thus, for example, entering two closing\ndelimiters at the end of a quoted string will render one of the characters\nliterally; that is, it will be considered as part of the quoted string's\ncontent.\n\nInside double quotes, `%`-strings are processed unless the `%` is escaped by\ndoubling it.  Double quotes inside these nested strings must also be escaped.\n\nNo other escaping takes place in quoted strings.\n\n=== Quoted Strings Examples\n\n- `'foo'` contains *foo*.\n\n- `foo'bar'` is read verbatim, so it contains *foo'bar'*.\n\n- `foo%|bar|` is read verbatim, so it contains *foo%|bar|*.\n\n- `'foo''bar'` is a single word whose content is *foo'bar*.\n\n- `\"baz\"\"\"` is a single word whose content is *baz\"*.\n\n- `%|foo||bar|` is a single word whose content is *foo|bar*.\n\n- `\"foo %|\"\"bar| %%,baz,\"` is a single word whose content is *foo \"bar %,baz,*.\n\n== Balanced Strings\n\nIf a word starts with `%X` with `X` a _nestable_ punctuation character (one\nof `(`, `[`, `{` and `<`), it is parsed as a balanced string whose closing\ndelimiter matches that of its opening delimiter (respectively, `)`, `]`,\n`}`, and `>`).\n\nThere is no way to escape the opening and closing characters, even if they\nare nested inside some other kind of string.\n\n=== Balanced Strings Examples\n\n- `%{foo}` contains *foo*.\n\n- `%{foo\\{bar}}` contains *foo\\{bar}*.\n\n- `foo%{bar}` contains *foo%{bar}*.\n\n- `\"foo %{bar}\"` is a single word whose content is *foo bar*.\n\n- `%{foo\\{}` is a parse error, since the `{}` delimiters are not balanced.\n\n- `%[foo\\{]` contains *foo\\{*, since it uses different delimiters.\n\n== Non-Quoted words\n\nOther words are non-quoted.  Non-quoted words are terminated by either a\nwhitespace or a `;`.\n\nIf they start with a `\\` followed by a `%`, `'`, or `\"`, then that leading\n`\\` escapes those characters and is discarded.\n\nIf a whitespace or `;` is preceded by a `\\`, then the `\\` is discarded, and\nthe whitespace or `;` becomes part of the word.  Any other `\\` is treated\nas a literal `\\`.\n\n== Typed Expansions\n\nQuoted and Balanced strings starting with `%` might have an optional\nalphabetic *expansion type* between the `%` and their delimiter (which is\nalways a punctuation character).  This *expansion type* defines how the\nstring's content is going to be expanded.  Rules for expanding and escaping\nexpansion types are the same as for `%`-strings.\n\n- If the *expansion type* is empty, the string content is used verbatim.\n\n- If the *expansion type* is one of `sh`, `reg`, `opt`, `val` or `arg`,\n  the string is expanded as described in <<expansions#,`:doc expansions`>>.\n\n- For any other *expansion type*, a parsing error is raised.\n"
  },
  {
    "path": "doc/pages/commands.asciidoc",
    "content": "= Commands\n\nSome commands take an exclamation mark (*!*), which can be used to force\nthe execution of the command (i.e. to quit a modified buffer, the\ncommand *q!* has to be used). Aliases are mentioned below each command.\n\nFor all commands that accept switches, a parameter beginning with\na single dash (*-*) will be interpreted as a switch. Any unknown switches\nwill cause an error. To force the remaining parameters to not be interpreted as\nswitches even if they begin with a dash, you can pass two dashes (*--*) as a parameter.\n\n*doc* <topic>::\n    *alias* help +\n    display documentation about a topic. The completion list displays the\n    available topics\n\n== Files and Buffers\n\nFor the following *write* commands, the *-sync* switch forces the synchronization\nof the file onto the filesystem\n\n*arrange-buffers* <buffer>...::\n    Reorder the buffers in the buffers list.\n    The named buffers will be moved to the front of the buffer list, in the order\n    given. Buffers that do not appear in the parameters will remain at the\n    end of the list, keeping their current order.\n\n*change-directory* [<directory>]::\n    *alias* cd +\n    change the current directory to *directory*, or the home directory if\n    unspecified\n\n*edit[!]* [<switches>] <filename> [<line> [<column>]]::\n    *alias* e +\n    open buffer on file, go to given line and column. If file is already\n    opened, just switch to this file. Use edit! to force reloading\n\n    *-debug*:::\n        The new buffer (if any) will be created as a debug buffer.\n        (See <<buffers#debug-buffers,`:doc buffers debug-buffers`>>)\n\n    *-existing*:::\n        If the named file does not exist, fail instead of creating a new buffer.\n\n    *-readonly*:::\n        The new buffer (if any) will be set read-only.\n\n    *-fifo* <fifoname>:::\n        Creates a new scratch buffer named <filename>, and continually appends\n        data from the fifo (named pipe) <fifoname> as it arrives.\n        (See <<buffers#fifo-buffers,`:doc buffers fifo-buffers`>>)\n\n    *-scratch*:::\n        Creates a new buffer named <filename>, which doesn't correspond to any\n        file on disk. If no filename is given, the buffer name will be\n        generated based on format `\\*scratch-$ID*`, where `$ID` is an\n        integer automatically incremented for new buffers.\n        (See <<buffers#scratch-buffers,`:doc buffers scratch-buffers`>>)\n\n    *-scroll*:::\n        If used with `-fifo`, when new data arrives Kakoune will scroll the\n        buffer down to make the new data visible.\n        Otherwise, does nothing.\n\n\n*write[!]* [-force] [-sync] [-method <writemethod>] [<filename>]::\n    *alias* w +\n    write buffer to <filename> or use its name if filename is not\n    given. If the file is write-protected, its permissions are temporarily\n    changed to allow saving the buffer and restored afterwards when\n    the write! command is used.\n\n    *-force*:::\n        Equivalent to `!`, allow overwiting existing files if `<filename>`\n        is given and set permissions temporarily if necessary.\n\n    *-sync*:::\n        Synchronise the filesystem after the write\n\n    *-method <writemethod>*:::\n        Enforce write method instead of relying on the `writemethod` option\n\n        `replace`::::\n            Write to a temporary file then rename to the target file so that\n            the modification appears atomically.\n\n        `overwrite`::::\n            Open the existing file and overwrite its content with the new\n            content.\n\n        (See <<options#builtin-options,`:doc options builtin-options`>>)\n\n*write-all* [-sync] [-method <writemethod>]::\n    *alias* wa +\n    write all changed buffers that are associated with a file\n\n*quit[!]* [<exit status>]::\n    *alias* q +\n    exit Kakoune, use quit! to force quitting even if there is some\n    unsaved buffer remaining. If specified, the client exit status\n    will be set to <exit status>\n\n*write-quit[!]* [-sync] [-method <writemethod>] [<exit status>]::\n    *alias* wq +\n    write current buffer and quit current client. If specified, the client\n    exit status will be set to <exit status>\n\n*write-all-quit* [-sync] [-method <writemethod>] [<exit status>]::\n    *alias* waq +\n    write all buffers and quit. If specified, the client exit status\n    will be set to <exit status>\n\n*buffer* <name>::\n    *alias* b +\n    switch to buffer <name>\n\n*buffer-next*::\n    *alias* bn +\n    switch to the next buffer.\n    Debug buffers are skipped.\n    (See <<buffers#debug-buffers,`:doc buffers debug-buffers`>>)\n\n*buffer-previous*::\n    *alias* bp +\n    switch to the previous buffer.\n    Debug buffers are skipped.\n    (See <<buffers#debug-buffers,`:doc buffers debug-buffers`>>)\n\n*delete-buffer[!]* [<name>]::\n    *alias* db +\n    delete current buffer or the buffer <name> if specified\n\n*rename-buffer* [-file|-scratch] <name>::\n    set current buffer name, if *-scratch* or *-file* is given, ensure\n    the buffer is set to the corresponding type.\n\n*source* <filename> <param>...::\n    execute commands in <filename>\n    parameters are available in the sourced script as `%arg{0}`, `%arg{1}`, …\n\n== Clients and Sessions\n\n*rename-client* <name>::\n    set current client name\n    Client names may only contain letters, numbers, `-`, and `_`.\n\n*rename-session* <name>::\n    set current session name\n\n*kill[!]* [<exit status>]::\n    terminate the current session, all the clients as well as the server.\n    If specified, the server and clients exit status will be set to <exit status>\n\n== Options\n\n*declare-option* [<switches>] <type> <name> [<value>]::\n    *alias* decl +\n    declare a new option, the -hidden switch hides the option in completion\n    suggestions (See <<options#declare-option,`:doc options declare-option`>>)\n\n*set-option* [<switches>] <scope> <name> <value>::\n    *alias* set +\n    change the value of an option in *scope*\n    (See <<options#set-option,`:doc options set-option`>>\n    and <<scopes#,`:doc scopes`>>)\n\n*unset-option* <scope> <name>::\n    *alias* unset +\n    unset the value of an option in *scope*, so the value from an outer scope\n    is used\n    (See <<options#unset-option,`:doc options unset-option`>>\n    and <<scopes#,`:doc scopes`>>)\n\n*update-option* <scope> <name>::\n    update the value of an option if its type supports that operation\n    (See <<options#update-option,`:doc options update-option`>>\n    and <<scopes#,`:doc scopes`>>)\n\n== Commands and Keys\n\n*define-command* [<switches>] <name> <command>::\n    *alias* def +\n    define a new command (See <<declaring-new-commands,Declaring new commands>>)\n\n*alias* <scope> <name> <command>::\n    define a new alias named *name* in *scope*\n    (See <<aliases,Using aliases>> and <<scopes#,`:doc scopes`>>)\n\n*complete-command* [<switches>] <name> <type> [<param>]::\n    *alias* compl +\n    configure how a command completion works\n    (See <<configuring-command-completion,Configuring command completion>>)\n\n*unalias* <scope> <name> [<command>]::\n    remove an alias if its current value is the same as the one passed\n    as an optional parameter, remove it unconditionally otherwise\n    (See <<aliases,Using aliases>> and <<scopes#,`:doc scopes`>>)\n\n*evaluate-commands* [<switches>] <command> ...::\n    *alias* eval +\n    evaluate commands, as if they were entered in the command prompt\n    (See <<execeval#,`:doc execeval`>>)\n\n*execute-keys* [<switches>] <key> ...::\n    *alias* exec +\n    execute a series of keys, as if they were hit (See <<execeval#,`:doc execeval`>>)\n\n*map* [<switches>] <scope> <mode> <key> <keys>::\n    bind a list of keys to a combination (See <<mapping#,`:doc mapping`>>\n    and <<scopes#,`:doc scopes`>>)\n\n*unmap* <scope> <mode> <key> [<expected>]::\n    unbind a key combination (See <<mapping#,`:doc mapping`>>\n    and <<scopes#,`:doc scopes`>>)\n\n*declare-user-mode* <name>::\n    declare a new user keymap mode\n\n*enter-user-mode* [<switches>] <name>::\n    enable <name> keymap mode for next key\n\n    *-lock*:::\n        stay in mode until `<esc>` is pressed\n\n== Hooks\n\n*hook* [<switches>] <scope> <hook_name> <filtering_regex> <command>::\n    execute *command* whenever a *hook_name* is triggered in *scope*\n    (See <<hooks#,`:doc hooks`>> and <<scopes#,`:doc scopes`>>)\n\n    *-group <groupname>*:::\n        Add this hook to the *groupname* group, so it can be removed by the\n        `remove-hooks` command (below)\n        or disabled with the `disabled_hooks` option\n        (see <<options#builtin-options,`:doc options builtin-options`>>).\n\n    *-once*:::\n        This hook will be automatically removed after it has been executed.\n\n    *-always*:::\n        This hook will run even while hooks are disabled.\n        See <<hooks#disabling-hooks,`:doc hooks disabling-hooks`>>.\n\n*remove-hooks* <scope> <group>::\n    *alias* rmhooks +\n    remove every hook in *scope* whose group matches the regex *group*\n    (See <<hooks#,`:doc hooks`>> and <<scopes#,`:doc scopes`>>)\n\n*trigger-user-hook* <param>::\n    trigger the `User` hook with the given *param* as filter string in\n    the current context. (See <<hooks#,`:doc hooks`>>)\n\n== Display\n\n*echo* [<switches>] <text>::\n    show *text* in status line, with the following *switches*:\n\n    *-markup*:::\n        expand the markup strings in *text* (See\n        <<faces#markup-strings,`:doc faces markup-strings`>>)\n\n    *-debug*:::\n        print the given text to the `\\*debug*` buffer\n\n    *-to-file* <filename>:::\n        write the given text to the given file on the host\n        filesystem.\n\n    *-to-shell-script* <script>:::\n        execute the given shell script with the given text\n        written to its standard input\n\n    *-quoting* <quoting>:::\n        define how arguments are quoted in echo output:\n\n        - *raw* (default)::::\n            just join each argument with a space\n\n        - *kakoune*::::\n            also wrap each argument in single quotes, doubling-up\n            embedded quotes.\n\n        - *shell*::::\n            also wrap each arguments in single quotes and escape\n            embedded quotes in a shell compatible way.\n\n*set-face* <scope> <name> <facespec>::\n    *alias* face +\n    define a face in *scope*\n    (See <<faces#,`:doc faces`>> and <<scopes#,`:doc scopes`>>)\n\n*unset-face* <scope> <name>::\n    Remove a face definition from *scope*\n    (See <<faces#,`:doc faces`>> and <<scopes#,`:doc scopes`>>)\n\n*colorscheme* <name>::\n    load named colorscheme +\n    Colorschemes are just `.kak` scripts that set faces. Colorschemes are searched for in `%val{config}/colors` and `%val{runtime}/colors`. +\n    (See <<faces#, `:doc faces`>> and <<expansions#, `:doc expansions`>>)\n\n*add-highlighter* [<switches>] <highlighter_path> <highlighter_parameters> ...::\n    *alias* addhl +\n    add a highlighter to the current window\n    (See <<highlighters#,`:doc highlighters`>>)\n\n*remove-highlighter* <highlighter_path>::\n    *alias* rmhl +\n    remove the highlighter whose id is *highlighter_id*\n    (See <<highlighters#,`:doc highlighters`>>)\n\n== Helpers\n\nKakoune provides some helper commands that can be used to define composite\ncommands in scripts. They are also available in the interactive mode,\nbut not really useful in that context.\n\n*prompt* [<switches>] <prompt> <command>::\n    prompt the user for a string, when the user validates, executes the\n    command. The entered text is available in the `text` value accessible\n    through `$kak_text` in shells or `%val{text}` in commands.\n\n    The *-init <str>* switch allows setting initial content, the\n    *-password* switch hides the entered text and clears the register\n    after command execution.\n\n    The *-on-change* and *-on-abort* switches, followed by a command\n    will have this command executed whenever the prompt content changes\n    or the prompt is aborted, respectively.\n\n    Completion support can be controlled with the same switches provided\n    by the *define-command* command, see\n    <<declaring-new-commands,Declaring new commands>>.\n\n    For *-shell-script-completion* and *-shell-script-candidates*\n    completions, token_to_complete will always be 1, and the full\n    prompt content will be passed as a single token. In other words,\n    word splitting does not take place.\n\n    NOTE: The prompt is displayed in and receives input from the\n    current client context, so inside a draft context like\n    `evaluate-commands -draft`, it is invisible and only responds to\n    an `execute-keys` command in the same context.\n\n*on-key* <command>::\n    wait for next key from user, then execute <command>, the key is\n    available through the `key` value, accessible through `$kak_key`\n    in shells, or `%val{key}` in commands.\n\n    NOTE: The key press must come from the current client context,\n    so inside a draft context like `evaluate-commands -draft`, it only\n    responds to an `execute-keys` command in the same context.\n\n*info* [<switches>] <text>::\n    display text in an information box with the following *switches*:\n\n    *-anchor* <line>.<column>:::\n        print the text at the given coordinates\n\n    *-style* <style>:::\n        set the style and placement of the message box.\n\n        *menu*::::\n            display the info next to the displayed menu, as documentation\n            for the currently selected entry.\n\n        *above*::::\n            display the info above the given anchor\n\n        *below*::::\n            display the info below the given anchor\n\n         *modal*::::\n             display the info modally, and do not auto-close the\n             info or replace it with non modal info boxes. To hide\n             a modal info box, use `info -style modal` with no\n             arguments.\n\n    *-title* <text>:::\n        set the title of the message box\n\n    *-markup*:::\n        parse markup in both title (if provided) and text. (See\n        <<faces#markup-strings,`:doc faces markup-strings`>>)\n\n    NOTE: The info box is displayed in the current client context,\n    so inside a draft context like `eval -draft`, it is invisible.\n\n*try* <commands> [catch <on_error_commands>]...::\n    prevent an error in *commands* from aborting the whole command\n    execution, execute *on_error_commands* instead. If nothing is to be\n    done on error, the catch part can be omitted. If an error is raised\n    in the *on_error_commands*, that error is propagated, except if\n    another *catch* and *on_error_commands* parameter follows, in which\n    case those commands get executed, and so-on. During error commands,\n    the description of the last raised error is available as `$kak_error`\n    in the shell, or `%val{error}` in commands.\n\n*nop*::\n    does nothing, but arguments will be evaluated (e.g. shell expansion)\n\n*fail* <text>::\n    raise an error, uses <text> as its description\n\n*set-register* <name> <contents>...::\n    *alias* reg +\n    set register *name* to *content*, each content parameter is assigned to\n    a different string in the register. (See <<registers#,`:doc registers`>>)\n\n*select* [<switches>] <anchor_line>.<anchor_column>,<cursor_line>.<cursor_column>...::\n    replace the current selections with the ones described in the arguments\n\n    *-timestamp* <timestamp>:::\n        specify which buffer timestamp those coordinates apply to. Uses current\n        buffer timestamp if not specified.\n\n    *-codepoint*::\n        provided columns are to be interpreted as codepoint counts, not byte counts.\n\n    *-display-column*::\n        provided columns are to be interpreted as display column counts, not byte counts.\n\n    both *-codepoint* and *-display-column* are only valid if *-timestamp*\n    matches the current buffer timestamp (or is not specified).\n\n*debug* {info,buffers,options,memory,shared-strings,profile-hash-maps,faces,mappings}::\n    print some debug information in the `\\*debug*` buffer\n\n== Module commands\n\nIn Kakoune, modules are a grouping of stored commands to be executed the first time\nthey are needed. This allows complex configurations to be evaluated lazily, and allows\nplugins to ensure their dependencies have been loaded before they execute. The builtin\nfiletype handling for Kakoune is implemented via modules.\n\n*provide-module* [<switches>] <name> <commands>::\n    declares a module *name* that is defined by *commands*. *commands* will be\n    evaluated as if by source the first time *require-module <name>* is run.\n\n    *-override*:::\n        allow the module to replace an existing one with the same name. Fails if\n        the module has already been evaluated.\n\n*require-module* <name>::\n    guarantees the commands associated with *name* have been evaluated before\n    continuing command execution. Fails if *name* has not been defined by a\n    *provide-module* command. Does nothing if the associated commands have\n    already been evaluated.\n\n== Multiple commands\n\nCommands (c.f. previous sections) can be chained, by being separated either\nby new lines or by semicolons, as such a semicolon must be escaped with a\nbackslash (\\;) to be considered as a literal semicolon argument.\n\nTo avoid trouble while writing `map` or `execute-keys` commands in scripts,\nthe alternative key namings `<semicolon>` and `<a-semicolon>` can be used.\n\n== Declaring new commands\n\nNew commands can be defined using the *define-command* command:\n\n*define-command* [<switches>] <command_name> <commands>::\n    *commands* is a string containing the commands to execute, and *switches*\n    can be any combination of the following parameters:\n\n    *-params* <num>:::\n        the command accepts a *num* parameter, which can be either a number,\n        or of the form <min>..<max>, with both <min> and <max> omittable\n\n    *-override*:::\n        allow the new command to replace an existing one with the same name\n\n    *-hidden*:::\n        do not show the command in command name completions\n\n    *-docstring*:::\n        define the documentation string for the command\n\n    *-menu*:::\n    *-file-completion*:::\n    *-client-completion*:::\n    *-buffer-completion*:::\n    *-command-completion*:::\n    *-shell-completion*:::\n    *-shell-script-completion*:::\n    *-shell-script-candidates*:::\n        old-style command completion specification, function as-if\n        the switch and its eventual parameter was passed to the\n        *complete-command* command\n        (See <<configuring-command-completion,Configuring command completion>>)\n\n        The use of those switches is discouraged in favor of the\n        *complete-command* command.\n\nUsing shell expansion allows defining complex commands or accessing\nKakoune's state:\n\n---------------------------------------------------------------------\n# create a directory for current buffer if it does not exist\ndefine-command mkdir %{ nop %sh{ mkdir -p $(dirname $kak_buffile) } }\n---------------------------------------------------------------------\n\n== Configuring command completion\n\nCommand completion can be configured with the *complete-command* command:\n\n*complete-command* [<switches>] <command_name> <completion_type> [<parameter>]::\n    *switches* can be:\n\n    *-menu*:::\n        the suggestions generated by the completion options are the only\n        permitted parameters. Kakoune will autoselect the best completion\n        candidate on command validation.\n\n    *completion_type* can be:\n\n    *file*:::\n        try file completion on any parameter passed to the command\n\n    *client*:::\n        try client name completion on any parameter passed to the command\n\n    *buffer*:::\n        try buffer name completion on any parameter passed to the command\n\n    *command*:::\n        try command completion on any parameter passed to the command\n\n    *shell*:::\n        try shell command completion on any parameter passed to the command\n\n    *shell-script*:::\n        following string is a shell command which takes parameters as\n        positional params and outputs one completion candidate per line.\n        The provided shell command will run after each keypress.\n        During the execution of the shell command, the following env vars are\n        available:\n\n        *$kak_token_to_complete*::::\n            Index of the token being completed in the command line.\n            Note that unlike the Unix `argv` tradition,\n            0 is the first argument, not the command name itself.\n\n        *$kak_pos_in_token*::::\n            Position of the cursor inside the token being completed, in bytes\n            from token start.\n\n    *shell-script-candidates*:::\n        following string is a shell script which takes parameters as\n        positional params and outputs one completion candidate per line.\n        The provided shell script will run once at the beginning of each\n        completion session, candidates are cached and then used by kakoune\n        internal fuzzy engine.\n\n        During the execution of the shell script, the following env vars are\n        available:\n\n        *$kak_token_to_complete*::::\n            Index of the token being completed in the command line.\n            Note that unlike the Unix `argv` tradition,\n            0 is the first argument, not the command name itself.\n\n== Aliases\n\nWith `:alias`, commands can be given additional names.\nAs aliases are intended to be used interactively most of the time,\nthey are often short. For example `:reg` is an alias for `:set-register`.\n\nThey are scoped, so that an alias can refer to one command for a buffer,\nand to another for another buffer. For instance `:next` could be an alias\nfor `grep-next-match` in a `*grep*` buffer while pointing to\n`:make-next-error` in a `*make*` buffer.\n\nThe following command defines `<alias>` as an alias for `<command>`:\n\n--------------------------------\n:alias <scope> <alias> <command>\n--------------------------------\n\n`<scope>` can be one of `global`, `buffer` or `window`.\n\n-------------------------------------\n:unalias <scope> <alias> [<expected>]\n-------------------------------------\n\nWill remove the given alias in the given scope. If `<expected>` is specified\nthe alias will only be removed if its current value is `<expected>`.\n\n"
  },
  {
    "path": "doc/pages/execeval.asciidoc",
    "content": "= Execute-keys and Evaluate-commands\n\n*execute-keys* [<switches>] <key> ...::\n    Run keys as if they were pressed.\n\n*evaluate-commands* [<switches>] <command> ...::\n    Evaluate specified commands as if they were entered into the command\n    prompt.\n\nBy default, the execution of both commands happens within the context of\nthe current client, and stops when the last key/command is reached or an\nerror occurs.\n\nWithout the *-save-regs* switch, *execute-keys* saves the following registers, which\nare then restored when the keys have been executed: */*, *\"*, *|*, *^*,\n*@*, *:*. *evaluate-commands* doesn't save any registers by default.\n(See <<registers#,`:doc registers`>>)\n\n== Switches for both commands\n\n*-client* <names>::\n    Execute in the context of each client specified in the comma separated\n    list *names*. '\\*' can be used as *names* to iterate overall clients.\n\n*-try-client* <name>::\n    Execute in the context of the client *name* if such client exists,\n    or else in the current context.\n\n*-draft*::\n    Execute in a copy of the context of the selected client. Modifications to\n    the selections or input state will not affect the client. This permits\n    making modifications to the buffer without modifying the user’s\n    selection.\n\n*-itersel*::\n    Execute once per selection, each having its own context. This prevents\n    situations where selections get merged.\n\n*-buffer* <names>::\n    Execute in the context of each buffer specified in the comma separated\n    list *names*. '\\*' can be used as *names* to iterate over all non-debug\n    buffers.\n    (See <<buffers#debug-buffers, `:doc buffers`>>)\n\n*-save-regs* <regs>::\n    *regs* is a string of registers to be restored after execution (overwrites\n    the list of registers saved by default).\n\n== Switches specific to *evaluate-commands*\n\n*-no-hooks*::\n    Disable hook execution while executing the keys/commands.\n    (See <<hooks#disabling-hooks,`:doc hooks`>>)\n\n*-verbatim*::\n    Don't reparse and split positional arguments. Forward them exactly\n    as specified.\n\n== Switches specific to *execute-keys*\n\n*-with-maps*::\n    Use a custom key mapping instead of the built-in one.\n    (See <<mapping#,`:doc mapping`>>)\n\n*-with-hooks*::\n    Execute keys and trigger existing hooks.\n    (See <<hooks#,`:doc hooks`>>)\n\n== Local scope in *evaluate-commands*\n\nWhen using *evaluate-commands* a new scope, named `local` is inserted.\nSee <<scopes#,`:doc scopes`>>\n"
  },
  {
    "path": "doc/pages/expansions.asciidoc",
    "content": "= Expansions\n\nWhile parsing a command (see <<command-parsing#,`:doc command-parsing`>>),\nKakoune recognises certain patterns and will replace them with their\nassociated value before executing the command. These patterns are called\nexpansions.\n\nEvery expansion consists of a `%`, followed by the expansion _type_ (one\nor more alphabetic characters), a quoting character, and then all the text\nup to and including its matching character.\n\nIf a nestable punctuation character (`(`, `[`, `{`, or `<`) is used as the\nopening quoting character, the expansion will end at its matching opposite\n(`)`, `]`, `}`, or `>`). Nested pairs of the braces used in the expansion are\nallowed, but they must be balanced. Braces other than the ones used in the\nexpansion need not be balanced, however. For example, `%{nest{ed} non[nested}`\nis valid and expands to `nest{ed} non[nested`.\n\nIf any other character is used, the expansion will end at the next occurrence of\nthat character. The quoting character can be escaped inside the expansion if it\nis doubled-up. For example, `%|abc||def|` expands to the text `abc|def`.\n\nIt doesn't matter which character is used, but `{}` are most common.\n\nThere are 2 types of quoting which can be used to group together words separated\nby whitespace into a single argument or prevent expansions from expanding:\n\n\"double quoted strings\"::\n    Double quoted strings are mainly for grouping multiple `%` expansions or\n    `%` expansions and regular text as a single argument. `%` and `\"` can be\n    escaped by doubling the characters (i.e. `%%` and `\"\"`).\n\n'single quoted strings'::\n    Expansions are not processed inside single quoted strings. Single quotes can\n    be escaped by doubling up (i.e. `''`).\n\nExpansions are processed when unquoted and anywhere inside double-quoted\nstrings, but not inside unquoted words, inside single-quoted strings, or\ninside %-strings or other expansions (see\n<<command-parsing#typed-expansions, `:doc command-parsing typed-expansions`>>\nfor full details). For example:\n\n* `echo %val{session}` echoes the current session ID\n\n* `echo x%val{session}x` echoes the literal text `x%val{session}x`\n\n* `echo '%val{session}'` echoes the literal text `%val{session}`\n\n* `echo \"x%val{session}x\"` echoes the current session ID, surrounded by `x`\n\n* `echo %{%val{session}}` echoes the the literal text `%val{session}`\n\n* `echo %sh{ echo %val{session} }` echoes the literal text `%val{session}`\n\nLike \"variable expansion\" and \"command substitution\" in shell programming,\nKakoune expansions can expand to multiple \"words\" - that is, separate\narguments on the resulting command-line. However, unlike shell programming,\nKakoune expansions cannot _accidentally_ expand to multiple words because they\ncontain whitespace or other special characters. Only expansions which\nsemantically contain a list of values (list-type options, registers, selections,\netc.) expand to multiple arguments. While in shell-programming it's good\npractice to always wrap expansions in double-quotes, in Kakoune it's perfectly\nsafe to leave expansions unquoted.\n\n== Argument expansions\n\nExpansions with the type `arg` can only be used inside the \"commands\" parameter\nof the `define-command` command (See <<commands#declaring-new-commands,`:doc\ncommands declaring-new-commands`>>).\n\nThe following expansions are available:\n\n*%arg{n}*::\n     (where _n_ is a decimal number) +\n     expands to argument number _n_ of the current command\n\n*%arg{@}*::\n    expands to all the arguments of the current command, as individual words\n\n== Option expansions\n\nExpansions with the type `opt` expand to the value associated with the named\noption in the current scope (See <<options#,`:doc options`>>).\n\nFor example, `%opt{BOM}` expands to `utf8` or to `none`, according to the\ncurrent state of the `BOM` option.\n\n== Register expansions\n\nExpansions with the type `reg` expand to the contents of the named\nregister. For registers named after symbols (like the search register\n`/`), the expansion can use either the symbol or the alphabetic name (See\n<<registers#,`:doc registers`>>).\n\nFor example, `%reg{/}` expands to the content of the `/` register, and so does\n`%reg{slash}`.\n\n== Shell expansions\n\nExpansions with the type `sh` are executed as shell-scripts, and whatever\nthe script prints to standard output replaces the expansion. For example,\nthe command `echo %sh{date}` will echo the output of the `date` command.\n\nKakoune will block user input until the script is complete. If you want to\nrun a process in the background, note that Kakoune will also wait for the\nscript's stdout and stderr to be closed. You can redirect stdout and stderr\nof your long running background process to avoid blocking.\n\nTIP: If a shell expansion writes to standard error, that output is appended to\nKakoune's `\\*debug*` buffer. If you're trying to debug a shell expansion,\ncheck the debug buffer with `:buffer \\*debug*` to see if anything shows up.\n\nBecause Kakoune does not expand expansions inside the text of an expansion,\nyou can't use normal expansions inside `%sh{}`. Instead, Kakoune can export\nexpansions as environment variables to make them available to the shell.\nHere's how expansion patterns map to variable names:\n\n*%arg{n}*::\n    (where _n_ is a decimal number) +\n    becomes `$_n_`. For example, `%arg{3}` becomes `$3`.\n\n*%arg{@}*::\n    becomes `$@`\n\n*%opt{x}*::\n    becomes `$kak_opt_x`\n\n*%reg{x}*::\n    (where _x_ is the alphabetic name of a register) +\n    `$kak_reg_x` contains all the selections in register _x_ +\n    `$kak_main_reg_x` contains only the main selection\n\n*%val{x}*::\n    becomes `$kak_x`\n\nValues can be quoted with a shell compatible quoting by using `$kak_quoted_`\nas a prefix, this is mostly useful for list-type options and registers, as\nit allows to correctly work with lists where elements might contains\nwhitespaces:\n\n----\neval set -- \"$kak_quoted_selections\"\nwhile [ $# -gt 0 ]; do\n    # ... do a thing with $1 ...\n    shift\ndone\n----\n\nThe `eval` command will take the expanded `$kak_quoted_selections`\nand unquote them, then execute the resulting `set` command, which sets\nthe shell's argument variables to the items from `$kak_selections`. The\n`while` loop with `shift` iterates through the arguments one by one.\n\nOnly variables actually mentioned in the body of the shell expansion or\nin passed arguments will be exported into the shell's environment.\n\nFor example:\n\n----\necho %sh{ env | grep ^kak_ }\n----\n\n... will not find any of Kakoune's special environment variables, but:\n\n----\necho %sh{ env | grep ^kak_ # kak_session }\n----\n\n... will find the `$kak_session` variable because it was mentioned by name\nin a comment, even though it wasn't directly used.\n\n----\ndefine-command -params .. eval-shell %{ echo %sh{ eval \"$@\" } }\neval-shell 'echo $kak_session'\n----\n\n... will also find the `$kak_session` variable because it was mentioned by name\nin the command arguments, which are automatically made available to shell blocks\nas \"$@\"\n\nTIP: These environment variables are also available in other contexts where\nKakoune uses a shell command, such as the `|`, `!` or `$` normal mode commands\n(See <<keys#,`:doc keys`>>).\n\n=== Command and Response fifo\n\nInside shell expansions, `$kak_command_fifo` refers to a named pipe that\naccepts Kakoune commands to be executed as soon as the fifo is closed. This\nnamed pipe can be opened and closed multiple times which makes it possible\nto interleave shell and Kakoune commands. `$kak_response_fifo` refers to\na named pipe that can be used to return data from Kakoune.\n\n----\n%sh{\n    echo \"write $kak_response_fifo\" > $kak_command_fifo\n    content=\"$(cat $kak_response_fifo)\"\n}\n----\n\nThis also makes it possible to pass data bigger than the system environment\nsize limit.\n\n== File expansions\n\nExpansions with the type `file` will expand to the content of the filename\ngiven in argument as read from the host filesystem.\n\nFor example, this command stores the entire content of `/etc/passwd` into the\n`a` register.\n\n----\nset-register a %file{/etc/passwd}\n----\n\n== Value expansions\n\nExpansions with the type `val` give access to Kakoune internal data that is\nnot stored in an option or a register. Some value expansions can only be used\nin certain contexts, like `%val{hook_param}` that expands to the parameter\nstring of the currently-executing hook, and is not available outside a hook.\n\nThe following expansions are supported (with required context _in italics_):\n\n*%val{buffile}*::\n    _in buffer, window scope_ +\n    full path of the file or same as `%val{bufname}` when there’s no\n    associated file\n\n*%val{buf_line_count}*::\n    _in buffer, window scope_ +\n    number of lines in the current buffer\n\n*%val{buflist}*::\n    list of the names of currently-open buffers (as seen in %val{bufname})\n\n*%val{bufname}*::\n    _in buffer, window scope_ +\n    name of the current buffer\n\n*%val{client_env_X}*::\n    _in window scope_ +\n    value of the `$X` environment variable in the client displaying the current\n    window (e.g. `%val{client_env_SHELL}` is `$SHELL` in the client's\n    environment)\n\n*%val{client_list}*::\n    unquoted list of the names of clients (as seen in `%val{client}`)\n    connected to the current session\n\n*%val{client}*::\n    _in window scope_ +\n    name of the client displaying the current window\n\n*%val{client_pid}*::\n    _in window scope_ +\n    process id of the client displaying the current window\n\n*%val{config}*::\n    directory containing the user configuration\n\n*%val{count}*::\n    _in `map` command <keys> parameter and `<a-;>` from object menu_ +\n    current count when the mapping was triggered, defaults to 0 if no\n    count given\n\n*%val{cursor_byte_offset}*::\n    _in window scope_ +\n    offset of the main cursor from the beginning of the buffer (in bytes)\n\n*%val{cursor_char_column}*::\n    _in window scope_ +\n    1-based offset from the start of the line to the cursor position in\n    Unicode codepoints, which may differ from visible columns if the document\n    contains full-width codepoints (which occupy two columns) or zero-width\n    codepoints\n\n*%val{cursor_display_column}*::\n    _in window scope_ +\n    1-based offset from the start of the line to the cursor position in\n    display column, taking into account tabs and character width.\n\n*%val{cursor_char_value}*::\n    _in window scope_ +\n    unicode value of the codepoint under the main cursor\n\n*%val{cursor_column}*::\n    _in window scope_ +\n    1-based offset from the start of the line to the first byte of the\n    character under the main cursor (in bytes), the fourth component of\n    `%val{selection_desc}`\n\n*%val{cursor_line}*::\n    _in window scope_ +\n    line of the main cursor, the third component of `%val{selection_desc}`\n\n*%val{error}*::\n    _in `try` command's <on_error_commands> parameter_ +\n    the text of the error that cancelled execution of the <commands> parameter\n    (or the previous <on_error_commands> parameter)\n\n*%val{history}*::\n    _in buffer, window scope_ +\n    the full change history of the buffer, including undo forks, in terms\n    of `parent committed redo_child modification0 modification1 ...`\n    entries, where _parent_ is the index of the entry's predecessor (entry\n    0, which is the root of the history tree, will always have `-` here),\n    _committed_ is a count in seconds from Kakoune's steady clock's epoch\n    of the  creation of the history entry, _redo_child_ is the index of the\n    child which will be visited for `U` (always `-` at the leaves of the\n    history), and each _modification_ is presented as in\n    `%val{uncommitted_modifications}`.\n\n*%val{history_since_id}*::\n    _in buffer, window scope_ +\n    a partial history of the buffer in the same format as `%val{history}`\n    starting after entry _id_\n\n*%val{history_id}*::\n    _in buffer, window scope_ +\n    history id of the current buffer, an integer value which refers to a\n    specific buffer version in the undo tree (see also `%val{timestamp}`)\n\n*%val{hook_param_capture_n}*::\n    _in `hook` command <command> parameter_ +\n    text captured by capture group _n_, if the executing hook's filter regex\n    used capture groups\n\n*%val{hook_param}*::\n    _in `hook` command <command> parameter_ +\n    the complete parameter string of the executing hook\n\n*%val{modified}*::\n    _in buffer, window scope_ +\n    `true` if the buffer has modifications not saved, otherwise `false`\n\n*%val{object_flags}*::\n    _for commands executed from the object menu's `<a-;>` only_ +\n    a pipe-separted list of words including `inner` if the user wants\n    an inner selection, `to_begin` if the user wants to select to the\n    beginning, and `to_end` if the user wants to select to the end\n\n*%val{recording_register}*::\n    the register being recorded to if currently recording a macro, empty when not recording\n\n*%val{register}*::\n    _in `map` command <keys> parameter and `<a-;>` from the object menu_ +\n    current register when the mapping was triggered\n\n*%val{runtime}*::\n    the directory containing the kak support files, which is determined from\n    Kakoune's binary location if `$KAKOUNE_RUNTIME` is not set\n\n*%val{select_mode}*::\n    _for commands executed from the object menu's `<a-;>` only_ +\n    `replace` if the new selection should replace the existing, `extend`\n    otherwise\n\n*%val{selection}*::\n    _in window scope_ +\n    content of the main selection\n\n*%val{selections}*::\n    _in window scope_ +\n    quoted list of the contents of all selections\n\n*%val{selection_desc}*::\n    _in window scope_ +\n    range of the main selection, represented as `a.b,c.d` where _a_ is the\n    anchor line, _b_ is the number of bytes from the start of the line to the\n    anchor, _c_ is the cursor line (like `%val{cursor_line}`), _d_ is\n    the number of bytes from the start of the line to the cursor (like\n    `%val{cursor_column}`), and all are 1-based decimal integers\n\n*%val{selections_char_desc}*::\n    _in window scope_ +\n    unquoted list of the ranges of all selections, in the same format as\n    `%val{selection_desc}`, except that the columns are in codepoints rather\n    than bytes\n\n*%val{selections_display_column_desc}*::\n    _in window scope_ +\n    unquoted list of the ranges of all selections, in the same format as\n    `%val{selection_desc}`, except that the columns are in display columns rather\n    than bytes\n\n*%val{selections_desc}*::\n    _in window scope_ +\n    unquoted list of the ranges of all selections, in the same format as\n    `%val{selection_desc}`\n\n*%val{selection_length}*::\n    _in window scope_ +\n    length (in codepoints) of the main selection\n\n*%val{selections_length}*::\n    _in window scope_ +\n    unquoted list of the lengths (in codepoints) of the selections\n\n*%val{selection_count}*::\n    _in window scope_ +\n    the number of selections\n\n*%val{session}*::\n    name of the current session\n\n*%val{source}*::\n    _in `.kak` file_ +\n    path of the file currently getting executed (through the source command)\n\n*%val{text}*::\n    _in `prompt` command <command> parameter_ +\n    the text entered by the user in response to the `prompt` command\n\n*%val{timestamp}*::\n    _in buffer, window scope_ +\n    timestamp of the current buffer, an integer that increments each time the\n    buffer is modified, including undoing and redoing previous modifications\n    (see also `%val{history_id}`)\n\n*%val{uncommitted_modifications}*::\n    _in buffer, window scope_ +\n    a list of quoted insertions (in the format `+line.column|text`) and\n    deletions (`-line.column|text`) not yet saved to the history (e.g. typing\n    in insert mode before pressing `<esc>`), where _line_ is the 1-based line\n    of the change, _column_ is the 1-based _byte_ of the change start (see\n    `%val{cursor_column}`), and _text_ is the content of the insertion or\n    deletion (see also `%val{history}`)\n\n*%val{user_modes}*::\n    unquoted list of user modes.\n\n*%val{version}*::\n    version of the current Kakoune server (git hash or release name)\n\n*%val{window_height}*::\n    _in window scope_ +\n    height of the current Kakoune window\n\n*%val{window_width}*::\n    _in window scope_ +\n    width of the current Kakoune window\n\n*%val{window_range}*::\n    _in window scope_ +\n    list of coordinates and dimensions of the buffer-space\n    available on the current window, in the following format:\n    `<coord_y> <coord_x> <height> <width>`\n\nValues in the above list that do not mention a context are available\neverywhere.\n\nA value described as a \"quoted list\" will follow the rules of Kakoune string\nquoting (See <<command-parsing#,`:doc command-parsing`>>). An \"unquoted list\"\ncannot contain any special characters that would require quoting.\n\n== Recursive Expansions\n\nExpansions with the type `exp` expand their content, the same way doubly\nquoted strings do except that double quotes don't need to be escaped.\n"
  },
  {
    "path": "doc/pages/faces.asciidoc",
    "content": "= Faces\n\n== Declaration\n\nA *face* determines how text is displayed. It has a foreground color,\na background color, and some attributes. The value of a face has the\nfollowing format:\n\n-----------------------------------------------------------\n[fg_color][,bg_color[,underline_color]][+attributes][@base]\n-----------------------------------------------------------\n\nfg_color, bg_color, underline_color::\n    A color whose value can be:\n        A named color:::\n            *black*, *red*, *green*, *yellow*, *blue*, *magenta*, *cyan*,\n            *white* *bright-black*, *bright-red*, *bright-green*,\n            *bright-yellow* *bright-blue*, *bright-magenta*, *bright-cyan*,\n            *bright-white*\n        The color of the base face (see below):::\n            *default*\n        A hexadecimal value:::\n            *rgb:RRGGBB*, *rgba:RRGGBBAA*\n    If left unspecified, the value of *default* is used.\n    Alpha values are used to blend the face onto either its base or else onto\n    whatever color happens to be used at the moment. For technical reasons,\n    alpha values must be >16.\n\nattributes::\n    A string whose individual letters each set an attribute:\n        *u*:::\n            underline\n        *c*:::\n            curly underline\n            Note: This takes precedence over underline if both are specified.\n        *U*:::\n            double underline\n            Note: This takes precedence over underline and curly underline if\n            also specified.\n        *r*:::\n            reverse\n        *b*:::\n            bold\n        *B*:::\n            blink\n        *d*:::\n            dim\n        *i*:::\n            italic\n        *s*:::\n            strikethrough\n        *F*:::\n            final::::\n                Override the previous face instead of merging with it. Can\n                only be replaced if another face with the final attribute\n                is applied.\n        *f*:::\n            final foreground::::\n                Same as final, but only applies to a face's foreground color.\n        *g*:::\n            final background::::\n                Same as final, but only applies to a face's background color.\n        *a*:::\n            final attributes::::\n                Same as final, but only applies to a face's attributes\n                Note: this will not clear to the f/g/a attributes of the base\n                face, `+a` cannot be used to remove `+f` and/or `+g`.\n\nbase::\n    The face onto which other faces apply. Its value can be any face name,\n    as long as no cycle is introduced. A face can reference itself, in which\n    case it'll apply on top of the version of the parent scope.\n\n== Built-in faces\n\nThe following faces are used by color schemes to highlight certain areas of\nthe user interface:\n\n*Default*::\n    default colors:::\n        The default foreground and background colors. If the value of *default*\n        is used here, then the colors used will be your terminal's defaults.\n\n*PrimarySelection*::\n    Main selection face for every selected character except the cursor.\n\n*SecondarySelection*::\n    Secondary selection face for every selected character except the cursor.\n\n*PrimaryCursor*::\n    Cursor of the primary selection.\n\n*SecondaryCursor*::\n    Cursor of the secondary selection.\n\n*PrimaryCursorEol*::\n    Cursor of the primary selection when it lies on an EOL (end of line)\n    character.\n\n*SecondaryCursorEol*::\n    Cursor of the secondary selection when it lies on an EOL (end of line)\n    character.\n\n*MenuForeground*::\n    Face for items selected in menus.\n\n*MenuBackground*::\n    Face for items not selected in menus.\n\n*MenuInfo*::\n    Face for the additional information displayed when selecting items in menus.\n\n*Information*::\n    Face for windows and messages displaying other information.\n\n*InlineInformation*::\n    Face for windows and messages displaying inline information.\n\n*Error*::\n    Face for errors reported by Kakoune in the status line.\n\n*DiagnosticError*::\n    Face for errors reported by external tools in the buffer.\n\n*DiagnosticWarning*::\n    Face for warnings reported by external tools in the buffer.\n\n*StatusLine*::\n    Face for the status line.\n\n*StatusLineMode*::\n    Face for the current mode, except normal mode.\n\n*StatusLineInfo*::\n    Face for special information.\n\n*StatusLineValue*::\n    Face for special values (numeric prefixes, registers, etc.).\n\n*StatusCursor*::\n    Face for the status line cursor.\n\n*Prompt*::\n    Face for the prompt displayed on the status line.\n\n*BufferPadding*::\n    Face applied on the *~* characters that follow the last line of a buffer.\n\n=== Built-in highlighter faces\n\nThe following faces are used by built-in highlighters if enabled.\n(See <<highlighters#,`:doc highlighters`>>).\n\n*LineNumbers*::\n    Face used by the *number-lines* highlighter.\n\n*LineNumberCursor*::\n    Face used to highlight the line number of the main selection.\n\n*LineNumbersWrapped*::\n    Face used to highlight the line number of wrapped lines.\n\n*MatchingChar*::\n    Face used by the *show-matching* highlighter.\n\n*Whitespace*::\n    Face used by the *show-whitespaces* highlighter.\n\n*WrapMarker*::\n    Face used by the *wrap-marker* highlighter.\n\n== Markup strings\n\nIn certain contexts, Kakoune can understand markup strings, which are strings\ncontaining formatting information. In these strings, the {facename} syntax\nwill enable the face facename until another face gets activated, or the end\nof the string is reached.\n\nFor example, the following command displays the text \"default\" in the\n*Default* face, and \"error\" in the *Error* face:\n\n----\necho -markup 'default {Error}error{Default} default'\n----\n\nInside a markup string, a literal `{` character is written as `\\{`, and a\nliteral backslash (`\\`) character is written as `\\\\`.\n\nThe `{\\}` string disables markup processing for the rest of the line. It\ncan be used to avoid having to escape text that might be mistaken for markup\ninstructions.\n\nFor example, this will prevent any `{` in the current buffer name from being\nincorrectly interpreted as markup.\n\n----\necho -markup \"{Information}name:{\\} %val{bufname}\"\n----\n"
  },
  {
    "path": "doc/pages/faq.asciidoc",
    "content": "= FAQ\n\n== The project\n\n=== How to pronounce the name of the project and what does it mean ?\n\nThe name of the project is pronounced \"Kak-oon\", and is a word taken from a\nNew Caledonian dialect based on French. It means a hard blow, usually a punch,\nbut generally refers to a strike into which all of one's strength went.\n\n=== Is there going to be a Windows port of Kakoune ?\n\nAs many features provided by UNIX systems are missing or less efficient on\na Windows system, the incentive to porting the project to this operating\nsystem is low.\n\nMoreover, you can achieve pretty decent performance by using Kakoune on\nCygwin (which is officially supported).\n\n== Interfacing\n\n=== Can I use Kakoune as a pager ?\n\nKakoune can be used as a pager, either by setting the `PAGER` environment\nvariable to `kak`, or by writing data directly to its standard input using a\nshell pipeline.\n\n=== Are there any non-console based frontends available?\n\nNo graphical frontend is currently officially maintained, you can however\ntry experimental community-developed ones.\n\n=== Why are colors misrendered in my Kakoune clients?\n\nThe most probable cause for colors being misrendered is a widespread practice\nthat consists in setting the `TERM` environment variable in the shell's\nconfiguration file. This variable should be set by the terminal emulator,\nand not overridden with an arbitrary value, otherwise it might interfere\nwith general UI rendering on the terminal's window.\n\n=== I'm using `tmux` and colors look weird\n\nIf you're using a tool that doesn't support \"palette switching\", colors will\nstill be slightly off: they are being rounded down to values supported by the\nregular color palette of Kakoune. Fortunately, on recent versions of `tmux`\n(>=2.6), you can work around that by using the following configuration\nsettings:\n\n.~/.tmux.conf\n----\nset -g default-terminal \"tmux-256color\"\nset -ga terminal-overrides \",*col*:Tc\"\n----\n\nNote: `\\*col*` is a regular expression that matches your terminal's `$TERM`\nvalue, modify it if necessary, e.g. `xterm-termite`.\n\nNext, run the following command to create a local `terminfo` override:\n\n----\n$ tic /path/to/kakoune/contrib/tmux-256color.terminfo\n----\n\nFinally, quit all existing sessions (`tmux kill-server`), and restart `tmux`.\n\n=== Why do I see weird special characters (~T~@~U) around Clippy?\n\nYou need a UTF-8 compliant locale, in order to render special characters.\n\nThe way to do so might vary from distribution to another, but an easy way\nto verify that your locale is set correctly is to run the following command:\n\n----\n$ locale\n----\n\nThe output should look similar to the following (with your own\nlanguage/country code set, instead of `en_US`):\n\n----\nLANG=en_US.UTF-8\nLC_CTYPE=\"en_US.UTF-8\"\nLC_NUMERIC=\"en_US.UTF-8\"\nLC_TIME=\"en_US.UTF-8\"\nLC_COLLATE=\"en_US.UTF-8\"\nLC_MONETARY=\"en_US.UTF-8\"\nLC_MESSAGES=\"en_US.UTF-8\"\nLC_PAPER=\"en_US.UTF-8\"\nLC_NAME=\"en_US.UTF-8\"\nLC_ADDRESS=\"en_US.UTF-8\"\nLC_TELEPHONE=\"en_US.UTF-8\"\nLC_MEASUREMENT=\"en_US.UTF-8\"\nLC_IDENTIFICATION=\"en_US.UTF-8\"\nLC_ALL=\n----\n\nYou can also run the project's test suite, which should display errors if\nyour locale doesn't support UTF-8 rendering:\n\n----\n$ cd kakoune\n$ make test\n----\n\n=== Why does leaving insert mode take more than half a second in `tmux`?\n\nUpon hitting the escape key, `tmux` waits for a short period of time to\ndetermine whether it's part of a function or a meta key sequence. In order\nto fix this \"lag\", set the waiting period in your `tmux` configuration file\nto a short time, e.g. 25ms: `set -sg escape-time 25`\n\n=== Can I split the window to display different buffers in them?\n\nAs a fairly compliant follower of the UNIX philosophy, Kakoune does not\ntry to implement features that are best handled by separate, dedicated\ntools. Window splitting in terminals is a prime example of that\nconcept, where the editor provides commands to interact with several\nterminal multiplexers (e.g. `tmux`), as opposed to emulating their\nfunctionalities.\n\nIn order to open buffers in the same window simultaneously using `tmux`\n(or one of the supported multiplexers), run Kakoune in a `tmux` session,\nand simply use the `:new` command to spawn new clients as you would\nhave otherwise in an X11 environment.\n\n== Generic functionalities\n\n=== Something is wrong, how can I get more debug information?\n\nYou can get quite a lot of information out of the editor at runtime. One\nway is through the `:debug` command, which will print out statistics and\nstate data into the `\\*debug*` buffer:\n\n----\n:debug <command>\n----\n\nAnother way is to set flags on the `debug` option:\n\n----\n:set global debug <flags>\n----\n\nMake sure to read all possible values suggested by the completion engine,\nas you type out both commands in a prompt.\n\nIf you want to troubleshoot a crash, you need to compile the editor with\ndebug symbols enabled:\n\n----\n$ make debug=yes\n----\n\nThe resulting binary should produce a stacktrace that you can, afterwards,\npost in an issue in unmangled form (c.f. `c++filt`).\n\n=== How can I explore the filesystem the way Vim's NerdTree does?\n\nThe builtin file completion engine used when opening a file for editing\n(using the `:edit` command and letting the suggestions pop up in the menu\nbeneath) is more convenient than Vim's, which should suit basic needs.\n\nHowever, if you need an actual explorer to interact with the editor,\nyou can create a Kakoune script that will spawn the tool in question,\nwhich should in return send an \"edit\" command followed by the path of the\nfile you selected to the current Kakoune session (e.g. `echo \"eval -client\n$kak_client edit /path/to/file\" | kak -p $kak_session`).\n\n=== How do I automatically indent code, as Vim does with `=`?\n\nAs `Kakoune` doesn't parse the contents of the buffers, there is no builtin\nequivalent for this Vim feature. Use a formatter/prettifier dedicated to\nthe language you're using with the help of the `|` key.\n\nExample: `%|indent<ret>` to indent an entire buffer with C code.\n\nYou can also set the `formatcmd` option and use the `:format` command to\nformat the entire buffer.\n\n=== Can Kakoune automatically complete the parameters of my functions?\n\nAs mentioned in the above question about Vim's `=` key, Kakoune does not\nparse the contents of a buffer by itself, which makes it impossible for\nthe editor to propose candidates upon completion.\n\nHowever, support for such a feature can be achieved through the use of a\ndedicated tool, as is the case with `clang` and C code: you can use the\n`clang-enable-autocomplete` and `clang-complete` builtin commands whenever\nediting a C/C++ file, and completion will work on function parameters.\n\nOther language-support scripts implement this functionality in a similar way,\nfor example the `jedi` script for Python buffers.\n\nAnother way to get automatic parameter completion that doesn't depend on\nbuilt-in support in Kakoune is through the\nhttps://microsoft.github.io/language-server-protocol/[Language Server Protocol],\nfor which you can find implementations that interact with the editor.\n\n=== Why aren't widely known command line shortcuts such as <c-w> or <c-u> available in Kakoune?\n\nDespite their widespread availability in multiple tools, those shortcuts do\nnot fit the paradigm that Kakoune implements, which is based on selections\nfirst.\n\nHowever, you can easily declare key mappings in your configuration file\nto be able to use those control-based shortcuts in insert mode.\n(See <<mapping#,`:doc mapping`>>)\n\nAlso note that a subset of \"readline shortcuts\" is implemented for command\nprompts.\n\n=== Can I disable auto-indentation completely?\n\nAll the indentation hooks are conventionally named `<lang>-indent`, which\nallows us to use the `disabled_hooks` variable to disable indentation\nglobally with the following command: `set global disabled_hooks '.+-indent'`\n\n=== How to enable syntax highlighting?\n\nThe MIME type of the files opened in new buffers is detected using the\n`file` command, and syntax highlighting enabled automatically when\npossible.\n\n=== My file seems to be highlighted with the wrong colors, I thought syntax highlighting was detected automatically?\n\nThe `file` utility has several shortcomings, such as detecting the\nwrong MIME type for a file containing data with different syntax, e.g.\na Python script containing hardcoded HTML templates detected as an HTML\nfile.\n\nKakoune does its best to detect file types (using known extensions for a\ngiven format for instance), but not much can be done about those ambiguous\ncases. You might consider writing a custom `$HOME/.magic` file if needed.\n\n=== Can I disable syntax highlighting completely?\n\nSimilarly to the indentation hooks, the name format followed by the\nhighlighting hooks is `<lang>-highlight`. You can thus disable syntax\nhighlighting using the following command: `set global disabled_hooks\n'.+-highlight'`\n\n=== Can the cursor be rendered as a beam?\n\nRendering the cursor as a beam is a common feature of other modal editors,\nit however doesn't fit within Kakoune's selection-first paradigm.\n\nThere is a selection on screen at all times, containing either data selected\nby the user, or a newline character when the buffer is empty.\n\nA selection is bound by an anchor and a cursor. They can overlap, but\nultimately must both be placed *over* a character. A beam cursor placed\n*between* two characters doesn't fulfil that requirement, and is thus\nnot allowed.\n\n== The editing language\n\n=== The scripting language lacks keywords, when are you going to expand it?\n\nThe scripting language is the smallest subset of statements/keywords that\nallows users to write plugins, commands, mappings.\n\nIt's not intended to be a one-stop generic interface, but rather a glue\nbetween core Kakoune instructions and complex logic.\n\nOther editors generally come up with their own language or leverage existing\nones (for example, VimL, LUA), whereas Kakoune interacts with the shell,\nthrough `%sh{…}` scopes.\n\nAs arbitrary Kakoune data (options, selection etc.) can be shared with\nshell scopes through environment variables, users are free to process this\ndata with pure shell scripting, or whatever interpreter they desire.\n\n=== Why aren't there other scopes similar to `%sh{}` e.g. python?\n\nSupporting custom scopes would add hard dependencies to the project, which\nis too much of a drawback when balanced against the low cost of using\nan interpreter in a regular shell scope (e.g. `%sh{ python -c \"...\" }`).\nThe shell scope allows users to spawn any interpreter they want, for a minimal\ncost in terms of performance, it is therefore the reason why it's the only\none available by default.\n\n=== What shell is used to expand `%sh{}` scopes?\n\nThe server expands shell scopes using the `sh` binary, located in one of\nthe directories containing all the POSIX standard utilities. This list of\ndirectories is stored in a system configuration variable, and queried by\nKakoune at startup.\n\nIn most distributions, `/bin/sh` will end up being used.\n\n=== Why does a dot `.` in a regex select newline characters?\n\nData in buffers is a stream of characters, and newlines do not receive special\ntreatment compared to other characters, with regards to regex matching. In\norder to select data in a line without any trailing newline characters, one could\nuse the `[^\\n]+` pattern, which is arguably a good compromise when\nbalanced against the ability to select data over several lines.\n\nYou can instruct the regex engine to stop matching newline characters with\n`.` by disabling the appropriate flag (`(?S)`).\n\n=== Why does `a` extend the current selection, but `i` leaves it untouched?\n\nSelections are ranges of characters whose delimiters are an \"anchor\" and\na \"cursor\", and inserting characters is always done before the cursor in\ninsert mode.\n\nConsequently, using the append primitive (`a`) nudges the cursor forward to\nmake room for characters, effectively extending the current selection since\nthe anchor remains immobile, even when the anchor and the cursor are at the\nsame location. By opposition, using the insert primitive (`i`) merely adds\ncharacters before the cursor, which never modifies the current selection.\n\n=== How to apply changes to all open buffers?\n\nThe `:exec` and `:eval` commands can apply changes to a comma-separated\nlist of buffers, passed as argument to the `-buffer` flag.\n\nIn order to let the editor figure out which buffers are open, the special\nvalue `*` is accepted as a wildcard. For example, in order to reload all\nopen buffers:\n\n----\n:eval -buffer * e!\n----\n\n=== Why is the text I pasted into a buffer completely mangled?\n\nIn order to assist users with writing code, some buffers come with hooks that\nautomatically indent the text inserted. Pasting the contents of the clipboard\ninto a buffer in insert mode triggers this indentation functionality,\nresulting into mangled text.\n\nTo prevent that from happening:\n\n* disable hooks and enter insert mode with `\\i`\n* insert text into the buffer (e.g. paste the clipboard's contents)\n* exit insert-mode, restoring hooks with `<esc>`\n"
  },
  {
    "path": "doc/pages/highlighters.asciidoc",
    "content": "= Highlighters\n\n== Description\n\nManipulation of the displayed text is done through highlighters, which can\nbe added or removed with the following commands:\n\n-----------------------------------------------------------------\nadd-highlighter [-override] <path>/<name> <type> <parameters> ...\n-----------------------------------------------------------------\n\nand\n\n--------------------------------\nremove-highlighter <path>/<name>\n--------------------------------\n\n*path* is the name of a highlighter group, it is expressed as a */*\nseparated path starting with a scope. Scopes are *global*, *buffer*,\n*window* and *shared*\n\n*name* is the name of the highlighter, if name is omitted in\n`add-highlighter` (the path ends with a `/`), it will be auto-generated\nfrom the remaining parameters.\n\nif `-override` is specified and the given name already exists, that\nhighlighter is replaced with the new one.\n\n== Convenient highlighters\n\n*show-matching*::\n    highlight matching char of the character under the selections' cursor\n    using `MatchingChar` face, with the following *options*:\n\n    *-previous*:::\n        fall back to the character before the cursor when the cursor is not\n        over a matching char\n\n*show-whitespaces* [options]::\n    display symbols on top of whitespaces to make them more explicit\n    using the `Whitespace` face, with the following *options*:\n\n    *-lf* <separator>:::\n        a one character long separator that will replace line feeds,\n        or an empty string to ignore them.\n\n    *-spc* <separator>:::\n        a one character long separator that will replace spaces,\n        or an empty string to ignore them.\n\n    *-nbsp* <separator>:::\n        a one character long separator that will replace non-breakable spaces,\n        or an empty string to ignore them.\n\n    *-tab* <separator>:::\n        a one character long separator that will replace tabulations,\n        or an empty string to ignore them.\n\n    *-tabpad* <separator>:::\n        a one character long separator that will be appended to tabulations to honor the *tabstop* option\n\n    *-indent* <separator>:::\n        a one character long separator that will replace the first space in indentation \n        according to the *indentwidth* option, or an empty string to ignore them.\n        This will use the `WhitespaceIndent` face.\n\n    *-only-trailing*:::\n        only highlight whitespaces at the end of the line\n\n*number-lines* [options]::\n    show line numbers using the `LineNumbers`, `LineNumberCursor` and `LineNumbersWrapped` faces,\n    with the following *options*: \n\n    *-relative*:::\n        show line numbers relative to the main cursor line\n\n    *-full-relative*:::\n        same as relative but show main cursor line as 0\n\n    *-hlcursor*:::\n        highlight the cursor line with a separate face\n\n    *-separator* <separator text>:::\n        specify a string to separate the line numbers column from\n        the rest of the buffer (default is '|')\n\n    *-cursor-separator* <separator text>:::\n        identical to *-separator* but applies only to the line of the cursor\n        (default is the same value passed to *-separator*)\n\n    *-min-digits* <num>:::\n        always reserve room for at least *num* digits,\n        so text doesn't jump around as lines are added or removed\n        (default is 2)\n\n*wrap* [options]::\n    soft wrap buffer text at window width, with the following *options*:\n\n    *-word*:::\n        wrap at word boundaries instead of codepoint boundaries.\n\n    *-indent*:::\n        preserve line indent when wrapping.\n\n    *-width <max_width>*:::\n        wrap text at *max_width* if the window is wider.\n\n    *-marker <marker_text>*:::\n        prefix wrapped lines with *marker_text*; if *-indent* was given,\n        the marker_text is displayed into the indentation if possible.\n\n== General highlighters\n\n*fill* <face>::\n    fill using the given *face*, mostly useful with regions highlighters\n\n*column* <number> <face> [options]::\n    highlight column *number* with face *face*, with the following *options*:\n\n    *-ruler* <character>:::\n        a single character to display instead of empty or whitespace cells at\n        column *number*. If this option is provided, *face* will also not\n        apply to non-empty cells, and will only apply to *character*.\n\n*line* <number> <face>::\n    highlight line *number* with face *face*\n\n*regex* <regex> <capture_id>:<face> ...::\n    highlight a regex, takes the regex as first parameter, followed by\n    any number of face parameters.\n    This highlights C++ style comments in cyan, with an eventual 'TODO:'\n    in yellow on red background:\n\n--------------------------------------------------------------------\nadd-highlighter window/ regex //\\h*(TODO:)[^\\n]* 0:cyan 1:yellow,red\n--------------------------------------------------------------------\n\n    capture_id can be either the capture number, or its name if a\n    named capture is used in the regex (See\n    <<regex#groups, `:doc regex groups`>>)\n\n*dynregex* <expression> <capture_id>:<face> ...::\n    similar to regex, but expand (like a command parameter would) the\n    given expression before building a regex from the result.\n    This highlights all the current search matches in italic:\n\n-----------------------------------------------\nadd-highlighter window/ dynregex '%reg{/}' 0:+i\n-----------------------------------------------\n\n== Specs highlighters\n\nThe following highlighters are useful to add indicators like lint warnings,\ngit blame output or spelling mistakes.\nSee <<options#types,`:doc options types`>> for the format of `line-specs`\nand `range-specs`.\n\n*flag-lines* <face> <option_name>::\n    add columns in front of the buffer, and display the flags specified\n    in `line-specs` option, using <face>.\n    In this example two words will be added in the gutter: a blue Foo at\n    line 1 and a bold red/yellow Bar on line 3:\n\n------------------------------------------------------------------------\ndeclare-option line-specs my_flags\nset-option window my_flags %val{timestamp} '1|Foo' '3|{red,yellow+b}Bar'\nadd-highlighter window/ flag-lines blue my_flags\n------------------------------------------------------------------------\n\n*ranges* <option_name>::\n    use the data in the `range-specs` option of the given name to highlight\n    the buffer. The string part of each tuple of the range-specs is\n    interpreted as a *face* to apply to the range.\n    In this example the 3 first chars of the buffer will be colored in red:\n\n--------------------------------------------------------\ndeclare-option range-specs my_range\nset-option window my_range %val{timestamp} '1.1,1.3|red'\nadd-highlighter window/ ranges my_range\n--------------------------------------------------------\n\n*replace-ranges* <option_name>::\n    use the data in the `range-specs` option of the given name to highlight\n    the buffer. The string part of each tuple of the range-specs is\n    interpreted as markup string (see <<faces#markup-strings,\n    `:doc faces markup-strings`>>) and displayed in place of the range.\n    Here, the 3 first chars of the buffer will be replaced by the word 'red':\n\n--------------------------------------------------------\ndeclare-option range-specs my_range\nset-option window my_range %val{timestamp} '1.1,1.3|red'\nadd-highlighter window/ replace-ranges my_range\n--------------------------------------------------------\n\n== Highlighting Groups\n\nThe *group* highlighter is a container for other highlighters. A subgroup\ncan be added to an existing group or scope using:\n\n-----------------------------------\nadd-highlighter <path>/<name> group \n-----------------------------------\n\nOther highlighters can then be added to that group\n\n------------------------------------------------\nadd-highlighter <path>/<name> <type> <params>...\n------------------------------------------------\n\nIn order to specify which kinds of highlighters can be added to a\ngiven group, the *-passes* flag set can be passed along with the group\nname. Possible values for this option can be one or several (separated\nwith a pipe sign) of *colorize*, *move* or *wrap* (default: *colorize*):\n\n--------------------------------------------------------------\nadd-highlighter window/<name> group -passes colorize|move|wrap\n--------------------------------------------------------------\n\n== Regions highlighters\n\nA special highlighter provides a way to segment the buffer into regions,\nwhich are to be highlighted differently.\n\n-------------------------------------\nadd-highlighter <path>/<name> regions\n-------------------------------------\n\nIndividual region definitions can then be added to that highlighter\n\n----------------------------------------------------\nadd-highlighter <path>/<name>/<region_name> region \\\n    [-match-capture] [-recurse <recurse>]          \\\n    <opening> <closing> <type> <params>...\n----------------------------------------------------\n\n*opening*::\n    regex that defines the region start text\n\n*closing*::\n    regex that defines the region end text\n\n*recurse*::\n    regex that defines the text that matches recursively an end token\n    into the region, every match of *recurse* will consume a following\n    match of *closing* regex, preventing it from closing the region.\n\n*type* and *params*::\n    A highlighter type, and associated params, as they would be passed\n    to `add-highlighter` if they were not applied as a region.\n\nIf the *-match-capture* switch is passed, then region *closing* and *recurse*\nregex matches are considered valid for a given region opening match only if they\nmatched the same content for the capture 1 in the *opening* regex.\n\nThe *recurse* option is useful for regions that can be nested, for example\nthe following construct:\n\n----------\n%sh{ ... }\n----------\n\naccepts nested braces scopes ('{ ... }') so the following string is valid:\n\n----------------------\n%sh{ ... { ... } ... }\n----------------------\n\nThis region can be defined with:\n\n---------------------------------\nshell_expand -recurse \\{ %sh\\{ \\}\n---------------------------------\n\nRegions are matched using the left-most rule: the left-most region opening\nstarts a new region. When a region closes, the closest next opening start\nanother region.\n\nThat matches the rule governing most programming language parsing.\n\nA default region, that will apply its given highlighter to the segments of the\nbuffer that are not in any defined region, can be added with the *default-region*\nhighlighter type.\n\n-----------------------------------------------------------------------------\nadd-highlighter <path>/<name>/<region_name> default-region <type> <params>...\n-----------------------------------------------------------------------------\n\nMost programming languages can then be properly highlighted using a region\nhighlighter as root:\n\n-----------------------------------------------------------------\nadd-highlighter <path>/<lang> regions\nadd-highlighter <path>/<lang>/string region '\"' '\"' fill string\nadd-highlighter <path>/<lang>/comment region '//' '$' fill comment\nadd-highlighter <path>/<lang>/code default-region group\nadd-highlighter <path>/<lang>/code/variable regex ...\nadd-highlighter <path>/<lang>/code/function regex ...\n-----------------------------------------------------------------\n\n== Shared Highlighters\n\nHighlighters are often defined for a specific filetype, and it makes then\nsense to share the highlighters between all the windows on the same filetypes.\n\nHighlighters can be put in the shared scope in order to make them reusable.\n\n---------------------------------\nadd-highlighter shared/<name> ...\n---------------------------------\n\nThe common case would be to create a named shared group, or regions and then\nfill it with highlighters:\n\n---------------------------------------\nadd-highlighter shared/<name> group\nadd-highlighter shared/<name>/ regex ...\n---------------------------------------\n\nIt can then be referenced in a window using the ref highlighter.\n\n----------------------------------\nadd-highlighter window/ ref <name>\n----------------------------------\n\nThe ref can reference any named highlighter in the shared scope.\n"
  },
  {
    "path": "doc/pages/hooks.asciidoc",
    "content": "= Hooks\n\n== Description\n\nCommands can be registered to be executed when certain events arise. To\nregister a hook use the following command:\n\n------------------------------------------------------------------\nhook [<switches>] <scope> <hook_name> <filtering_regex> <commands>\n------------------------------------------------------------------\n\n*scope* can be one of *global*, *buffer* or *window* (See\n<<scopes#,`:doc scopes`>>).\n\n*hook_name* must be one of the hook names in the list below, like `InsertKey`\nor `BufSetOption`.\n\n*filtering_regex* must match the entire parameter string in order for the\ncommands to be executed.\n\n*command* is a string containing the commands to execute when the hook\nis called.\n\nFor *switches*, see <<commands#hooks,`:doc commands hooks`>>.\n\nIf a hook is registered with the `-group` switch, it can later be removed with\nthe `remove-hooks` command:\n\n----------------------------\nremove-hooks <scope> <group>\n----------------------------\n\nThis command removes all hooks originally registered in *scope* whose\n`-group` switch matches the regex *group*.\n\nFor example to automatically use line numbering with .cc files, use the\nfollowing command:\n\n--------------------------------------------------------------\nhook global WinCreate .*\\.cc %{ add-highlighter number-lines }\n--------------------------------------------------------------\n\n== Default hooks\n\nThe parameter string associated with each hook is described after the hook\nname. Hooks with no description will always use an empty string.\n\n*NormalIdle*::\n    a certain duration has passed since the last keypress in normal mode\n\n*NormalKey* `key`::\n    a key is received in normal mode. This hook will not trigger when the user\n    presses a key on the left-hand side of a normal-mode mapping (see\n    <<mapping#,`:doc mapping`>>), but will trigger for keys on the right-hand\n    side. See also `RawKey` below.\n\n*InsertIdle*::\n    a certain duration has passed since the last keypress in insert mode\n\n*InsertKey* `key`::\n    a key is received in insert mode. This hook will not trigger when the user\n    presses a key on the left-hand side of a insert-mode mapping (see\n    <<mapping#,`:doc mapping`>>), but will trigger for keys on the right-hand\n    side. See also `RawKey` below.\n\n*InsertChar* `char`::\n    a character is received in insert mode\n\n*InsertDelete* `deleted char`::\n    a character is deleted in insert mode\n\n*InsertMove* `move key`::\n    the cursor moved (without inserting) in insert mode\n\n*PromptIdle*::\n    a certain duration has passed since the last keypress in prompt mode\n\n*WinCreate* `buffer name`::\n    a window was created. This hook is executed in draft context, so any\n    changes to selections or input state will be discarded.\n\n*WinClose* `buffer name`::\n    a window was destroyed. This hook is executed in a draft context, so any\n    changes to selections or input state will be discarded.\n\n*WinResize* `<line>.<column>`::\n    a window was resized. This hook is executed in a draft context, so any\n    changes to selections or input state will be discarded.\n\n*WinDisplay* `buffer name`::\n    a client switched to displaying the given buffer.\n\n*WinSetOption* `<option_name>=<new_value>`::\n    an option was set in a window context. This hook is executed in a draft\n    context, so any changes to selections or input state will be discarded.\n\n*GlobalSetOption* `<option_name>=<new_value>`::\n    an option was set at the global scope\n\n*BufSetOption* `<option_name>=<new_value>`::\n    an option was set in a buffer context\n\n*BufNewFile* `filename`::\n    a buffer for a new file has been created\n\n*BufOpenFile* `filename`::\n    a buffer for an existing file has been created\n\n*BufCreate* `filename`::\n    a buffer has been created\n\n*BufWritePre* `filename`::\n    executed just before a buffer is written\n\n*BufWritePost* `filename`::\n    executed just after a buffer is written\n\n*BufReload* `filename`::\n    executed after a buffer reload has been triggered by an external\n    modification to its file\n\n*BufClose* `buffer name`::\n    executed when a buffer is deleted, while it is still valid\n\n*BufOpenFifo* `buffer name`::\n    executed when a buffer opens a fifo\n\n*BufReadFifo* `<start line>.<start column>,<end line>.<end column>`::\n    executed after some data has been read from a fifo and inserted in\n    the buffer. The hook param contains the range of text that was just\n    inserted, in a format compatible with the `select` command.\n\n*BufCloseFifo*::\n    executed when a fifo buffer closes its fifo file descriptor either\n    because the buffer is being deleted or the writing end has been closed\n\n*ClientCreate* `client name`::\n    executed when a new client is created.\n\n*ClientClose* `client name`::\n    executed when a client is closed, after it was removed from the client\n    list.\n\n*ClientRenamed* `<old name>:<new name>`::\n    executed when a client is renamed using the `rename-client` command\n\n*SessionRenamed* `<old name>:<new name>`::\n    executed when a session is renamed using the `rename-session` command\n\n*EnterDirectory* `path`::\n    executed on startup and when the current working directory is changed\n    using the `change-directory` command. The hook param is an absolute path\n    to the new working directory.\n\n*RuntimeError* `error message`::\n    an error was encountered while executing a user command\n\n*ModeChange* `[push|pop]:<old mode>:<new mode>`::\n    Triggered whenever a mode is pushed or removed from the mode stack.\n    The mode name can be things like 'normal' or 'insert' for regular\n    interactive modes, or 'next-key[<name>]' for sub-modes where Kakoune\n    prompts for a key. For example, `g` in normal mode pushes 'next-key[goto]'\n    mode, the `enter-user-mode foo` command pushes 'next-key[user.foo]' mode,\n    and the `on-key -mode-name bar` command pushes 'next-key[bar]' mode.\n\n*KakBegin* `session name`::\n    Kakoune has started, this hook is called just after reading the user\n    configuration files\n\n*KakEnd*::\n    Kakoune is quitting\n\n*FocusIn* `client name`::\n    on supported clients, triggered when the client gets focused\n\n*FocusOut* `client name`::\n    on supported clients, triggered when the client gets unfocused\n\n*InsertCompletionShow*::\n    Triggered when the insert completion menu gets displayed\n\n*InsertCompletionHide* `completion`::\n    Triggered when the insert completion menu gets hidden, the list of\n    inserted completions text ranges is passed as filtering text, in the\n    same format the `select` command expects.\n\n*RawKey* `key`::\n    Triggered whenever a key is pressed by the user, regardless of what mode\n    Kakoune is in, or what mappings are present (see\n    <<mapping#,`:doc mapping`>>). It cannot be triggered by `execute-keys`,\n    even with the `-with-hooks` option (see\n    <<execeval#execute-keys-specific-switches,`:doc execeval execute-keys-specific-switches`>>).\n\n*RegisterModified* `register`::\n    Triggered after a register has been written to.\n\n*ModuleLoaded* `module`::\n    Triggered after a module is evaluated by the first `require-module` call\n\n*User* `param`::\n    Triggered  via the `trigger-user-hook` command. Provides a way for plugins\n    to introduce custom hooks by specifying what *param* would be.\n\nNote that some hooks will not consider underlying scopes depending on what\ncontext they are bound to be run into, e.g. the `BufWritePost` hook is a buffer\nhook, and will not consider the `window` scope.\n\nWhile defining hook commands with a `%sh{}` block, some additional env\nvars are available:\n\n* `kak_hook_param`: filtering text passed to the currently executing hook\n\n* `kak_hook_param_capture_N`: text captured by the hook filter regex capturing\n    group N, N can either be the capturing group number, or its name\n    (See <<regex#groups,`:doc regex groups`>>).\n\n== Disabling Hooks\n\nHooks can be disabled temporarily by prefixing any normal mode command by `\\`\n(see <<keys#,`:doc keys`>>) and permanently by setting the `disabled_hooks` option\nwhich accepts a regex describing which hooks won't be executed. For example\nindentation hooks can be disabled with '.*-indent'.\n\nFinally, hook execution can be disabled while using the `execute-keys` or\n`evaluate-commands` commands by using the `-no-hooks` switch.\n(See <<execeval#,`:doc execeval`>>)\n\nAs an exception to these rules, hooks declared with the `-always` switch\nare triggered no matter what. A good use case is doing some cleanup on `BufCloseFifo`.\n"
  },
  {
    "path": "doc/pages/keymap.asciidoc",
    "content": "= Keymap\n\n---\n ┌───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┲━━━━━━━━━━━━━━┓\n │  upper│ cmdout│convtab│       │selpipe│sel all│       │  align│pattern│ rotate│ rotate│   trim│dup sel┃ ⇤            ┃\n ├┄┄CASE┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┨              ┃\n │  lower│ 1     │ 2     │ 3     │ 4     │ 5     │ 6     │ 7     │ 8     │ 9     │ 0     │       │       ┃              ┃\n ┢━━━━━━━┷━━━┱───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┺━━━┳━━━━━━━━━━┫\n ┃ ↹         ┃ record│     ᵐʷ│     ᵐʷ│  paste│     ᵐʳ│       │  redo │ INSERT│  above│ before│      ᵐ│      ᵐ┃          ┃\n ┃           ┠┄MACRO┄┤   next│   word├REPLACE┤to char├┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄OPEN┄┄┼┄PASTE┄┤ object│ object┃ ⏎        ┃\n ┃           ┃ replay│   word│    end│   char│       │  yank │  undo │ insert│  below│  after│  begin│    end┃          ┃\n ┣━━━━━━━━━━━┻━┱─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┴─┬─────┺━┓        ┃\n ┃ ⇬           ┃ APPEND│  split│       │     ᵐʳ│     ᵐᵍ│     ᵐˡ│      ᵐ│      ᵐ│     ᵐˡ│cmdline│use reg│   pipe┃        ┃\n ┃             ┠┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┤   find│  goto │       │       │       │       ├┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┨        ┃\n ┃             ┃ append│ select│ delete│   char│       │     ← │     ↓ │     ↑ │     → │ cursor│       │eschook┃        ┃\n ┣━━━━━━━━━┳━━━┹───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┬───┴───┲━━━┷━━━━━━━┻━━━━━━━━┫\n ┃         ┃ indent│   save│      ᵐ│copysel│      ᵛ│     ᵐʷ│     ᵐʳ│     ᵐʳ│ dedent│ indent│     ᵐʳ┃                    ┃\n ┃         ┠┄┄┄┄┄┄┄┼┄MARKS┄┤ select├┄┄┄┄┄┄┄┤   view│   prev│ search│  match├┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┤ search┃                    ┃\n ┃ ⇧       ┃ dedent│restore│   line│ change│   cmds│   word│   next│   char│clrsels│ repeat│       ┃ ⇧                  ┃\n ┣━━━━━━━━━┻┳━━━━━━┷━━━┳━━━┷━━━━━┱─┴───────┴───────┴───────┴───────┴───────┴─┲━━━━━┷━━━━┳━━┷━━━━━━━╋━━━━━━━━━┳━━━━━━━━━━┫\n ┃          ┃          ┃         ┃                                           ┃          ┃          ┃         ┃          ┃\n ┃          ┃          ┃         ┃                user mappings              ┃          ┃          ┃         ┃          ┃\n ┃Ctrl      ┃          ┃Alt      ┃                                           ┃AltGr     ┃          ┃         ┃Ctrl      ┃\n ┗━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━┹───────────────────────────────────────────┺━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━┻━━━━━━━━━━┛\n\n ʳ: alt reverses direction\n ʷ: alt uses WORD instead of word (that is non blank instead of [a-z_])\n ᵐ: move, shifted will append to selection, alt will use alternate behaviour.\n ˡ: alt goes to begin/end of line\n ᵍ: the g key prefixes different goto location functionalities, such as going\n    to begin/end of the file, opening the file whose name is selected, etc…\n ᵛ: the v key prefixes different view related functionalities, such as scrolling\n---\n"
  },
  {
    "path": "doc/pages/keys.asciidoc",
    "content": "= Keys\n\n== Key Syntax\n\nUsual keys are written using their ascii character, including capital\nkeys. Non printable keys use an alternate name, written between *<*\nand *>*, such as *<esc>* or *<del>*. Modified keys are written between\n*<* and *>* as well, with the modifier specified as either *c* for\nControl, *a* for Alt, or *s* for Shift, followed by a *-* and the key (either\nits name or ascii character), for example *<c-x>*, *<a-space>*, *<c-a-w>*.\n\nIn order to bind some keys to arbitrary ones, refer to <<mapping#,`:doc mapping`>>\n\n== Insert mode\n\n*<esc>*::\n    leave insert mode\n\n*<backspace>*::\n    delete characters before cursors\n\n*<del>*::\n    delete characters under cursors\n\n*<left>*, *<right>*, *<up>*, *<down>*::\n    move the cursors in given direction\n\n*<home>*::\n    move cursors to line begin\n\n*<end>*::\n    move cursors to end of line\n\n*<c-r>*::\n    insert contents of the register given by next key\n\n*<c-v>*::\n    insert next keystroke directly into the buffer, without interpreting it\n\n*<c-u>*::\n    commit changes up to now as a single undo group\n\n*<a-;>*, *<a-semicolon>*::\n    escape to normal mode for a single command\n\n== Insert mode completion\n\nThe `completers` option controls automatic completion, which kicks in when\nthe value specified in the `idle_timeout` option is reached.\n\n*<c-o>*::\n    toggle automatic completion\n\n*<c-n>*::\n    select next completion candidate\n\n*<c-p>*::\n    select previous completion candidate\n\n*<c-x>*::\n    explicit insert completion query, followed by:\n\n    *f*:::\n        explicit file completion\n\n    *w*:::\n        explicit word completion (current buffer)\n\n    *W*:::\n        explicit word completion (all buffers)\n\n    *l*:::\n        explicit line completion (current buffer)\n\n    *L*:::\n        explicit line completion (all buffers)\n\n== Using Counts\n\nIn normal mode, commands can be prefixed with a numeric `count`, which can control\nthe command behaviour.\n\nFor example, *3W* selects 3 consecutive words and *3w* select the third word on\nthe right of the end of each selection.\n\n== Disabling Hooks\n\nAny normal mode command can be prefixed with `\\` which will disable hook execution\nfor the duration for the command (including the duration of modes the command could\nmove to, so `\\i` will disable hooks for the whole insert session) (see\n<<hooks#,`:doc hooks`>>).\n\nAs autoindentation is implemented in terms of hooks, this can be used to disable\nit when pasting text.\n\n== Movement\n\n'word' is a sequence of alphanumeric characters, or those in the `extra_word_chars`\noption (see <<options#builtin-options,`:doc options builtin-options`>>).\n'WORD' is a sequence of non whitespace characters. Generally, a movement on its own\nwill move each selection to cover the text moved over, while holding down\nthe Shift modifier and moving will extend each selection instead.\n\n*h*::\n    select the character on the left of the end of each selection\n    `<left>` maps to this by default.\n    (See <<mapping#default-mappings,`:doc mapping default-mappings`>>)\n\n*j*::\n    select the character below the end of each selection\n    `<down>` maps to this by default.\n    (See <<mapping#default-mappings,`:doc mapping default-mappings`>>)\n\n*k*::\n    select the character above the end of each selection\n    `<up>` maps to this by default.\n    (See <<mapping#default-mappings,`:doc mapping default-mappings`>>)\n\n*l*::\n    select the character on the right of the end of each selection\n    `<right>` maps to this by default.\n    (See <<mapping#default-mappings,`:doc mapping default-mappings`>>)\n\n*w*::\n    select the word and following whitespaces on the right of the end of each selection\n\n*b*::\n    select preceding whitespaces and the word on the left of the end of each selection\n\n*e*::\n    select preceding whitespaces and the word on the right of the end of each selection\n\n*<a-[wbe]>*::\n    same as [wbe] but select WORD instead of word\n\n*f*::\n    select to the next occurrence of given character\n\n*t*::\n    select until the next occurrence of given character\n\n*<a-[ft]>*::\n    same as [ft] but in the other direction\n\n*<a-.>*::\n    repeat last object or *f*/*t* selection command\n\n*m*::\n    select to the next sequence enclosed by matching characters, see the\n    `matching_pairs` option in <<options#,`:doc options`>>\n\n*M*::\n    extend the current selection to the next sequence enclosed by matching\n    character, see the `matching_pairs` option in <<options#,`:doc options`>>\n\n*<a-m>*::\n    select to the previous sequence enclosed by matching characters, see the\n    `matching_pairs` option in <<options#,`:doc options`>>\n\n*<a-M>*::\n    extend the current selection to the previous sequence enclosed by matching\n    characters, see the `matching_pairs` option in <<options#,`:doc options`>>\n\n*x*::\n    expand selections to contain full lines (including end-of-lines)\n\n*<a-x>*::\n    trim selections to only contain full lines (not including last\n    end-of-line)\n\n*%*, *<percent>*::\n    select whole buffer\n\n*<a-h>*::\n    select to line begin\n    `<home>` maps to this by default.\n    (See <<mapping#default-mappings,`:doc mapping default-mappings`>>)\n\n*<a-l>*::\n    select to line end\n    `<end>` maps to this by default.\n    (See <<mapping#default-mappings,`:doc mapping default-mappings`>>)\n\n*<pageup>, <c-b>*::\n    scroll one page up\n\n*<pagedown>, <c-f>*::\n    scroll one page down\n\n*<c-u>*::\n    scroll half a page up\n\n*<c-d>*::\n    scroll half a page down\n\n*;*, *<semicolon>*::\n    reduce selections to their cursor\n\n*<a-;>*, *<a-semicolon>*::\n    flip the direction of each selection\n\n*<a-:>*::\n    ensure selections are in forward direction (cursor after anchor)\n\n== Changes\n\nYanking (copying) and pasting use the *\"* register by default (See <<registers#,`:doc registers`>>)\n\n*i*::\n    enter insert mode before selections\n\n*a*::\n    enter insert mode after selections\n\n*d*::\n    yank and delete selections\n\n*c*::\n    yank and delete selections and enter insert mode\n\n*.*::\n    repeat last insert mode change (*i*, *a*, or *c*, including the\n    inserted text)\n\n*<a-d>*::\n    delete selections (not yanking)\n\n*<a-c>*::\n    delete selections and enter insert mode (not yanking)\n\n*I*::\n    enter insert mode at the beginning of the lines containing\n    the start of each selection\n\n*A*::\n    enter insert mode at the end of the lines containing\n    the end of each selection\n\n*o*::\n    enter insert mode in a new line (or in a given `count` of new lines)\n    below the end of each selection\n\n*O*::\n    enter insert mode in a new line (or in a given `count` of new lines)\n    above the beginning of each selection\n\n*<a-o>*::\n    add an empty line below cursor\n\n*<a-O>*::\n    add an empty line above cursor\n\n*y*::\n    yank selections\n\n*p*::\n    paste after the end of each selection\n\n*P*::\n    paste before the beginning of each selection\n\n*<a-p>*::\n    paste all after the end of each selection, and select each pasted string\n\n*<a-P>*::\n    paste all before the start of each selection, and select each pasted string\n\n*R*::\n    replace selections with yanked text\n\n*<a-R>*::\n    replace selections with every yanked text\n\n*r*::\n    replace each character with the next entered one\n\n*<a-j>*::\n    join selected lines\n\n*<a-J>*::\n    join selected lines and select spaces inserted in place of line breaks\n\n*<a-_>*::\n    merge contiguous selections together (works across lines as well)\n\n*<+>*, *<plus>*::\n    duplicate each selection (generating overlapping selections)\n\n*<a-+>*, *<a-plus>*::\n    merge overlapping selections\n\n*>*, *<gt>*::\n    indent selected lines\n\n*<a-\\>>*, *<a-gt>*::\n    indent selected lines, including empty lines\n\n*<*, *<lt>*::\n    unindent selected lines\n\n*<a-<>*, *<a-lt>*::\n    unindent selected lines, do not remove incomplete indent (3 leading\n    spaces when indent is 4)\n\n*u*::\n    undo last change\n\n*U*::\n    redo last change\n\n*<c-j>*::\n    move forward in changes history\n\n*<c-k>*::\n    move backward in changes history\n\n*<a-u>*::\n    undo last selection change\n\n*<a-U>*::\n    redo last selection change\n\n*&*::\n    align selections, align the cursor of each selection by inserting spaces\n    before the first character of each selection\n\n*<a-&>*::\n    copy indent, copy the indentation of the main selection (or the\n    `count` one if a `count` is given) to all other ones\n\n*`*::\n    to lower case\n\n*~*::\n    to upper case\n\n*<a-`>*::\n    swap case\n\n*@*::\n    convert tabs to spaces in each selection, uses the buffer tabstop\n    option or the `count` parameter for tabstop\n\n*<a-@>*::\n    convert spaces to tabs in each selection, uses the buffer tabstop\n    option or the `count` parameter for tabstop\n\n*_*::\n    unselect whitespace surrounding each selection, drop those that only\n    contain whitespace\n\n*<a-)>*::\n    rotate selections content, if specified, the `count` groups selections,\n    so the following command\n\n----------\n    3<a-)>\n----------\n\n    rotates (1, 2, 3) and (3, 4, 6) independently\n\n*<a-(>*::\n    rotate selections content backward\n\n== Changes through external programs\n\nShell expansions are available, (See <<expansions#shell-expansions,`:doc expansions shell-expansions`>>)\nThe default command comes from the *|* register (See <<registers#,`:doc registers`>>)\n\n*|*::\n    pipe each selection through the given external filter program and\n    replace the selection with its output.\n\n*<a-|>*::\n    pipe each selection through the given external filter program and\n    ignore its output.\n\n*!*::\n    insert and select command output before each selection.\n\n*<a-!>*::\n    append and select command output after each selection.\n\n== Searching\n\nSearches use the */* register by default (See <<registers#,`:doc registers`>>)\n\n*/*::\n    select next match after each selection\n\n*<a-/>*::\n    select previous match before each selection\n\n*?*::\n    extend to next match after each selection\n\n*<a-?>*::\n    extend to previous match before each selection\n\n*n*::\n    select next match after the main selection\n\n*N*::\n    add a new selection with next match after the main selection\n\n*<a-n>*::\n    select previous match before the main selection\n\n*<a-N>*::\n    add a new selection with previous match before the main selection\n\n***::\n    set the search pattern to the main selection (automatically\n    detects word boundaries)\n\n*<a-***>*::\n    set the search pattern to the main selection (verbatim, no smart\n    detection)\n\n== Goto commands\n\n*g*, *G*::\n    When a `count` is specified, *G* only extends the selection to the given line,\n    *g* sends the anchor to the given line and a menu is then displayed which waits\n    for one of the following additional keys:\n\n    *h*:::\n        go to line begin\n\n    *l*:::\n        go to line end\n\n    *i*:::\n        go to non blank line start\n\n    *g*, *k*:::\n        go to the first line\n\n    *j*:::\n        go to the last line\n\n    *e*:::\n        go to last char of last line\n\n    *t*:::\n        go to the first displayed line\n\n    *c*:::\n        go to the middle displayed line\n\n    *b*:::\n        go to the last displayed line\n\n    *a*:::\n        go to the previous (alternate) buffer\n\n    *f*:::\n        open the file whose name is selected\n\n    *.*:::\n        go to last buffer modification position\n\n== View commands\n\n*v*, *V*::\n    *V* enters lock view mode (which will be left when the <esc> is hit),\n    and *v* modifies the current view; a menu is then displayed which waits\n    for one of the following additional keys:\n\n    *v*, *c*:::\n        center the main selection in the window (vertically)\n\n    *m*:::\n        center the main selection in the window (horizontally)\n\n    *t*:::\n        scroll to put the main selection on the top line of the window\n\n    *b*:::\n        scroll to put the main selection on the bottom line of the window\n\n    *<*:::\n        scroll to put the main cursor on the leftmost column of the window\n\n    *>*:::\n        scroll to put the main cursor on the rightmost column of the window\n\n    *h*:::\n        scroll the window `count` columns left\n\n    *j*:::\n        scroll the window `count` line downward\n\n    *k*:::\n        scroll the window `count` line upward\n\n    *l*:::\n        scroll the window `count` columns right\n\n== Marks\n\nCurrent selections position can be saved in a register and restored later on.\nMarks use the *^* register by default (See <<registers#,`:doc registers`>>)\n\n*Z*::\n    save selections to the register\n\n*z*::\n    restore selections from the register\n\n*<a-z>*, *<a-Z>*::\n    *<a-z>* combines selections from the register with the current ones, whereas\n    *<a-Z>* combines current selections with the ones in the register; a menu\n    is then displayed which waits for one of the following additional keys:\n\n    *a*:::\n        append selections\n\n    *u*:::\n        keep a union of selections\n\n    *i*:::\n        keep an intersection of selections\n\n    *<*:::\n        select the selection with the leftmost cursor for each pair\n\n    *>*:::\n        select the selection with the rightmost cursor for each pair\n\n    *+*:::\n        select the longest selection\n\n    *-*:::\n        select the shortest selection\n\n== Macros\n\nMacros use the *@* register by default (See <<registers#,`:doc registers`>>)\n\n*Q*::\n    start or end macro recording\n\n*q*::\n    play a recorded macro\n\n== Jump list\n\nSome commands, like the goto commands, buffer switch or search commands,\npush the previous selections to the client's jump list. It is possible\nto skim through the jump list using:\n\n*<c-i>*::\n    jump forward\n\n*<c-o>*::\n    jump backward\n\n*<c-s>*::\n    create a jump step, but also save the current selection to have it be\n    restored when the step is subsequently cycled through\n\n== Multiple selections\n\n*s*, *S*, *<a-k>* and *<a-K>* use the */* register by default (See <<registers#,`:doc registers`>>)\n\n*s*::\n    create a selection for each match of the given regex\n    (selects the count capture if it is given)\n\n*S*::\n    split selections with the given regex\n    (selects the count capture if it is given)\n\n*<a-s>*::\n    split selections on line boundaries\n\n*<a-S>*::\n    select first and last characters of each selection\n\n*C*::\n    duplicate selections on the lines that follow them\n\n*<a-C>*::\n    duplicate selections on the lines that precede them\n\n*,*::\n    clear selections to only keep the main one\n\n*<a-,>*::\n    clear the main selection\n\n*<a-k>*::\n    keep selections that match the given regex\n\n*<a-K>*::\n    clear selections that match the given regex\n\n*$*::\n    pipe each selection to the given shell command and keep the ones\n    for which the shell returned 0. Shell expansions are available,\n    (See <<expansions#shell-expansions,`:doc expansions shell-expansions`>>)\n\n*)*::\n    rotate main selection (the main selection becomes the next one)\n\n*(*::\n    rotate main selection backward (the main selection becomes the previous one)\n\n== Object Selection\n\nFor nestable objects, a `count` can be used in order to specify which surrounding\nlevel to select. Object selections are repeatable using *<a-.>*.\n\n=== Whole object\n\nA 'whole object' is an object *including* its surrounding characters.\nFor example, for a quoted string this will select the quotes, and\nfor a word this will select trailing spaces.\n\n*<a-a>*::\n    select the whole surrounding object\n\n*[*::\n    select to the whole surrounding object start\n\n*]*::\n    select to the whole surrounding object end\n\n*{*::\n    extend selections to the whole surrounding object start\n\n*}*::\n    extend selections to the whole surrounding object end\n\n*<a-A>:: select each whole nested object in selections\n\n=== Inner object\n\nAn 'inner object' is an object *excluding* its surrounding characters.\nFor example, for a quoted string this will *not* select the quotes, and\nfor a word this will *not* select trailing spaces.\n\n*<a-i>*::\n    select the surrounding inner object\n\n*<a-[>*::\n    select to the surrounding inner object start\n\n*<a-]>*::\n    select to the surrounding inner object end\n\n*<a-{>*::\n    extend selections to the surrounding inner object start\n\n*<a-}>*::\n    extend selections to the surrounding inner object end\n\n*<a-I>*::\n    select each inner nested object in selections\n\n=== Objects types\n\nAfter the keys described above, a second key needs to be entered\nin order to specify the wanted object:\n\n*b*, *(*, *)*::\n    select the enclosing parenthesis\n\n*B*, *{*, *}*::\n    select the enclosing {} block\n\n*r*, *[*, *]*::\n    select the enclosing [] block\n\n*a*, *<*, *>*::\n    select the enclosing <> block\n\n*Q*, *\"*::\n    select the enclosing double quoted string\n\n*q*, *'*::\n    select the enclosing single quoted string\n\n*g*, *`*::\n    select the enclosing grave quoted string\n\n*w*::\n    select the whole word\n\n*<a-w>*::\n    select the whole WORD\n\n*s*::\n    select the sentence\n\n*p*::\n    select the paragraph\n\n*␣*::\n    select the whitespaces\n\n*i*::\n    select the current indentation block\n\n*n*::\n    select the number\n\n*u*::\n    select the argument\n\n*c*::\n    select user defined object, will prompt for open and close text\n\n*<a-;>*, *<a-semicolon>*::\n    run a command with additional expansions describing the selection\n    context (See <<expansions#,`:doc expansions`>>)\n\nIf a punctuation character is entered, it will act as the delimiter.\nFor instance, if the cursor is on the `o` of `/home/bar`, typing\n`<a-a>/` will select `/home/`.\n\n== Prompt commands\n\nWhen pressing `:` in normal mode, Kakoune will open a prompt to enter\na command.  The executed command line is stored in the *:* register\n(See <<registers#,`:doc registers`>>).\n\nDuring editing, a transient *clipboard* is available, its content is\nempty at the start of prompt edition, and is not preserved afterwards.\n\nThe following keys are recognized by this mode to help with editing\n(See <<commands#,`:doc commands`>>).\n\n*<ret>*::\n    validate prompt\n\n*<esc>*::\n    abandon without validating prompt\n\n*<left>*, *<c-b>*::\n    move cursor to previous character\n\n*<right>*, *<c-f>*::\n    move cursor to next character\n\n*<home>*, *<c-a>*::\n    move cursor to first character\n\n*<end>*, *<c-e>*::\n    move cursor past the last character\n\n*<backspace>*, *<c-h>*::\n    erase character before cursor\n\n*<del>*, *<c-d>*::\n    erase character under cursor\n\n*<a-f>*::\n    advance to next word begin\n\n*<a-F>*::\n    advance to next WORD begin\n\n*<a-b>*::\n    go back to previous word begin\n\n*<a-B>*::\n    go back to previous WORD begin\n\n*<a-e>*::\n    advance to next word end\n\n*<a-E>*::\n    advance to next WORD end\n\n*<c-w>*::\n    erase to previous word begin, save erased content to *clipboard*\n\n*<c-W>*::\n    erase to previous WORD begin, save erased content to *clipboard*\n\n*<a-d>*::\n    erase to next word begin, save erased content to *clipboard*\n\n*<a-D>*::\n    erase to next WORD begin, save erased content to *clipboard*\n\n*<c-k>*::\n    erase to end of line, save erased content to *clipboard*\n\n*<c-u>*::\n    erase to begin of line, save erased content to *clipboard*\n\n*<c-y>*::\n    insert *clipboard* content before cursor\n\n*<up>*, *<c-p>*::\n    select previous entry in history\n\n*<down>*, *<c-n>*::\n    select next entry in history\n\n*<tab>*::\n    select next completion candidate\n\n*<s-tab>*::\n    select previous completion candidate\n\n*<c-r>*::\n    insert the content of the register given by next key, if next key has\n    the Alt modifier, it will insert all values in the register joined with\n    spaces, else it will insert the main one. if it has the Control modifier,\n    it will quote the inserted value(s).\n\n*<c-v>*::\n    insert next keystroke without interpreting it\n\n*<c-x>*::\n    explicit completion query, followed by:\n\n    *f*:::\n        explicit file completion\n\n    *w*:::\n        explicit word completion (from current buffer)\n\n*<c-o>*::\n    toggle automatic completion\n\n*<a-!>*::\n    expand the typed expansions in currently entered text\n    (See <<expansions#,`:doc expansions`>>)\n\n*<a-;>*, *<a-semicolon>*::\n    escape to normal mode for a single command\n\n== User commands\n\n*<space>*::\n    enter default `user mode` to access custom commands\n    (See <<modes#user-mode,`:doc modes user-mode`>>)\n\n== Cancelling operations\n\nThese keys are used to cancel long-running operations, either inside\nKakoune or outside it. Because they are intended as a safety mechanism\nwhen something goes wrong, these keys are handled very early on in\nKakoune's input processing, and therefore cannot be remapped in any mode.\n\n*<c-c>*::\n    Stop any external processes. If you ever see Kakoune display a message\n    like \"waiting for shell command to finish\", this key will stop\n    that command so you can regain control of Kakoune.\n    Technically, this key causes Kakoune to send the SIGINT signal\n    to its entire process group.\n\n*<c-g>*::\n    Cancel a long-running Kakoune operation, such as a regex search on a\n    very large file. This also clears Kakoune's input buffer, so any\n    commands that were waiting for the long-running operation to complete\n    will also be cancelled.\n"
  },
  {
    "path": "doc/pages/mapping.asciidoc",
    "content": "= Mapping\n\n== Description\n\nCreating and removing shortcuts boils down to the following commands,\nrespectively:\n\n---------------------------------------\nmap [switches] <scope> <mode> <key> <keys>\nunmap <scope> <mode> <key> [<expected>]\n---------------------------------------\n\nThe *map* command makes *key* behave as if the *keys* sequence was typed.\n\n*mode* dictates in what context the mapping will be available:\n\n    *insert*::\n        insert mode\n\n    *normal*::\n        normal mode\n\n    *prompt*::\n        prompts, such as when entering a command through *:*, or a regex through */*\n\n    *user*::\n        mode entered when the user prefix is hit (default: '<space>')\n\n    *goto*::\n        mode entered when the goto key is hit (default: 'g')\n\n    *view*::\n        mode entered when the view key is hit (default: 'v')\n\n    *object*::\n        mode entered when an object selection is triggered (e.g. '<a-i>')\n\nThe context of execution of the above modes is always the current one at the\ntime of execution of the mapping, except for *user* mode (always executed\nin a 'normal' context). Refer to <<modes#,`:doc modes`>> for more details.\n\nAn optional *-docstring* switch followed by a string can be used\nto describe what the mapping does. This docstring will be used\nin autoinfo boxes.\n\nThe *unmap* command removes a mapping of *key* in the given *scope* and\n*mode*. If *expected* is specified, the mapping is removed only if it is\nset to the same sequence of keys passed using the *expected* argument.\n\nFor more information about the values of the *scope* parameter, refer to\n<<scopes#,`:doc scopes`>>.\n\n== Mapping commands\n\nIt's common to use a normal-mode or user-mode mapping to trigger a command,\nlike this:\n\n----\nmap global user n :make-next-error<ret>\n----\n\nIf you make a normal-mode mapping, you can prefix it with a count or a register\nname like any other normal-mode key. You can forward this information to the\ncommand you invoke with the `%val{count}` and `%val{register}` expansions\n(See <<expansions#,`:doc expansions`>>). For example:\n\n----\nmap global normal = ':echo Got count %val{count} and reg %val{register}<ret>'\n----\n\n== Mappable keys\n\nSee <<keys#,`:doc keys`>> to learn what each key does in each mode. The keys on\nthe right-hand side of the mapping are not affected by other mappings, they\nalways perform their original function.\n\nFor *key* and *keys* in the *map* command, the following key names can\nbe used:\n\n*x*, *<x>*::\n    Most keys, especially alphabetic keys, represent themselves.\n    Keys can also be wrapped in angle-brackets for consistency\n    with the non-alphabetic keys below.\n\n*<c-x>*::\n    Holding down Control while pressing the *x* key.\n\n*<a-x>*::\n    Holding down Alt while pressing the *x* key.\n\n*<s-x>*, *X*, *<X>*, *<s-X>*::\n    Holding down Shift while pressing the *x* key.\n    *<s-x>*, *<s-X>* and *<X>* are treated as the same key. The *s-* modifier\n    only works with ASCII letters and cannot be used with other printable keys\n    (non-ASCII letters, digits, punctuation) because their shift behaviour\n    depends on your keyboard layout. The *s-* modifier _can_ be used with\n    special keys like *<up>* and *<tab>*.\n\n*<c-a-x>*::\n    Holding down Control and Alt while pressing the *x* key.\n\n*<lt>*, *<gt>*::\n    The *<* and *>* characters.\n\n*<plus>*, *<minus>*::\n    The *+* and *-* characters.\n\n*<ret>*::\n    The Return or Enter key.\n\n*<space>*::\n    The space bar.\n\n*<tab>*::\n    The Tab key.\n\n*<backspace>*::\n    The Backspace (delete to the left) key.\n\n*<del>*::\n    The Delete (to the right) key.\n\n*<esc>*::\n    The Escape key.\n\n*<up>*, *<down>*, *<left>*, *<right>*::\n*<pageup>*, *<pagedown>*, *<home>*, *<end>*::\n    The usual cursor-movement keys.\n\n*<ins>*::\n    The Insert key.\n\n*<F1>*, *<F2>*, ...*<F12>*::\n    Function keys.\n\n*<semicolon>*, *<percent>*, *<quote>*, *<dquote>*::\n    The *;*, *%*, *'* and *\"* characters, these keys allow reducing the amount\n    of backslash escaping in scripts (for example, `exec \\%` becomes `exec\n    <percent>`)\n\nNOTE: Although Kakoune allows many key combinations to be mapped, not every\npossible combination can be triggered. For example, due to limitations in\nthe way terminals handle control characters, mappings like *<c-s-a>* are\nunlikely to work in Kakoune's terminal UI.\n\nSome keys, like `<c-c>` and `<c-g>`, cannot be remapped because they are\nused to cancel operations. See <<keys#cancelling-operations,`:doc keys cancelling-operations`>>.\n\n== Default mappings\n\nSome mappings exist by default in the global scope:\n\nIn normal mode:\n\n  * `<left>`  maps to `h`\n  * `<right>` maps to `l`\n  * `<up>`    maps to `k`\n  * `<down>`  maps to `j`\n  * `<home>`  maps to `<a-h>`\n  * `<end>`   maps to `<a-l>`\n\nShift version of those mappings exist as well\n(for example `<s-left>` maps to `H`).\n"
  },
  {
    "path": "doc/pages/modes.asciidoc",
    "content": "= Modes\n\n== Description\n\nKakoune is a modal editor which means that keys have different effects depending\non the current mode. Therefore, modes can be conceptualized as a way to group\nrelated behaviors together during a text editing workflow.\n\nKakoune starts in Normal mode. A few keys let users enter other modes\nwhere they can focus on a specific task before going back to Normal mode.\n\nModes are stored in a stack with the top of the stack being the active mode.\nSo in some scenarios, the Normal mode may feel *nested* in another one.\nThe `ModeChange` hook is triggered each time a mode is popped or pushed\non this stack. See <<hooks#,`:doc hooks`>>\n\nTo get a comprehensive list of commands available for each modes, see\n<<keys#,`:doc keys`>>. Most of them are described in *info* boxes in\nreal-time if the `autoinfo` option is set.\n\nTo customize key mappings in various modes, refer to <<mapping#,`:doc mapping`>>.\n\n== Builtin modes\n\n=== Normal mode\n\nNormal mode is the default mode. It provides commands to manipulate\nselections, such as general movement, text object selection, searching,\nsplitting, and commands to manipulate the text underlying the current\nselections, such as yanking, pasting, deleting…\n\nIt also provides commands to enter other modes.\n\nSee normal commands <<keys#movement,`:doc keys movement`>>.\n\n=== Insert mode\n\nInsert mode provides an efficient way to interactively write text into\nthe buffer. Most keys will insert their corresponding characters before\nevery selections cursors. It also provides completion support for inserted\ntext along with some commands for basic movement.\n\nInsert mode can be entered from Normal modes through a set of commands\nsuch as `i` to insert before selection, `a` to insert after selection,\n`o` to insert in a new line below selections… It can then be exited with\n`<esc>` to return to Normal mode.\n\nSee changes <<keys#changes,`:doc keys changes`>>.\n\nFrom Insert mode, `<a-;>` provides a way to temporarily enter a new\nNormal mode for a single command, before returning to Insert mode.\n\nSee insert commands <<keys#insert-mode,`:doc keys insert-mode`>>.\n\n=== Goto mode\n\nGoto mode provides commands dedicated to jumping either inside a buffer\n(such as jumping to buffer start/end, window top/bottom) or to another\n(such as jumping to the file whose path is currently selected).\n\nSee goto commands <<keys#goto-commands,`:doc keys goto-commands`>>.\n\n=== View mode\n\nView mode provides commands dedicated to controlling the window, such\nas scrolling or centering the main selection cursor.\n\nSee view commands <<keys#view-commands,`:doc keys view-commands`>>.\n\n=== Prompt mode\n\nMode entered with `:`, `/` or the `prompt` command. During prompt mode a\nline of text is edited and then validated with `<ret>` or abandoned with\n`<esc>`.\n\nSee prompt commands <<keys#prompt-commands,`:doc keys prompt-commands`>>.\n\n=== Object mode\n\nMode entered with `<a-i>`, `<a-a>` and various combinations of `[]{}` keys.\nIt aims at crafting semantic selections, often called *text-objects*.\n\nSee object commands <<keys#object-selection,`:doc keys object-selection`>>.\n\n=== User mode\n\nMode entered with `<space>`. The user mode is empty by default and is the\nopportunity to store custom mappings with no risk to shadow builtin ones.\nThe context of execution is always the Normal mode.\n\n== User modes\n\nThe following two commands are useful in advanced use cases, when the\nbuiltin User mode gets too crowded by mappings competing for the same\nkey that deserves to be split in more meaningful collections. It's\nmostly useful for plugin authors who want to bind their new commands in\nextensible menus.\n\n--------------------------------\ndeclare-user-mode <name>\n--------------------------------\n\nDeclare a new custom mode that can later be referred by the *map* command.\nFor example a `grep` custom mode to attach keys like `n` or `p` to skim\nthrough the output results.\n\n-------------------------------\nenter-user-mode <name>\n-------------------------------\n\nEnable the designated mode for the next key. Docstrings are shown in the\nautomatic info box to increase discoverability. To keep illustrating\nthe aforementioned fictional `grep` mode, a normal mapping on `<a-g>`\ncould be used to enter this mode.\n"
  },
  {
    "path": "doc/pages/options.asciidoc",
    "content": "= Options\n\n== Description\n\nKakoune can store named and typed values that can be used both to\ncustomize the core editor behaviour, and to store data used by extension\nscripts.\n\n[[set-option]]\nOptions can be modified using the `set-option` command:\n\n--------------------------------------------\nset-option [-add|-remove] <scope> <name> <values>...\n--------------------------------------------\n\n<scope> can be *global*, *buffer*, *window* or *current* (See\n<<scopes#,`:doc scopes`>>). *current* relates to the narrowest scope in\nwhich the option is already set.\n\nWhen the option is a list or a map, multiple <values> can be given as\nseparate arguments, or can be omitted altogether in order to empty the\noption.\n\nIf `-add` or `-remove` is specified, the new value is respectively *added*\nto or *removed* from the current one instead of replacing it (the exact\noutcome depends on the type, see below).\n\n[[unset-option]]\nOption values can be unset in a specific scope with the `unset-option`\ncommand:\n\n---------------------------\nunset-option <scope> <name>\n---------------------------\n\nUnsetting an option will make it fallback to the value of its parent scope,\nhence options cannot be unset from the *global* scope.\n\n[[declare-option]]\nNew options can be declared using the `declare-option` command:\n\n---------------------------------------------------\ndeclare-option [-hidden] <type> <name> [<value>...]\n---------------------------------------------------\n\nIf `-hidden` is specified, the option will not be displayed in completion\nsuggestions.\n\n[[update-option]]\nCertain option type can be *updated*, usually to match potential changes\nin the buffer they relate to. This can be triggered by the `update-option`\ncommand:\n\n----------------------------\nupdate-option <scope> <name>\n----------------------------\n\n== Types\n\nAll options have a type, which defines how they are translated to/from\ntext and their set of valid values.\n\nSome types are usable for user defined options while some other types\nare exclusively available to built-in options.\n\n*int*::\n    an integer number.\n\n    `set -add` performs a math addition. +\n    `set -remove` performs a math substraction. +\n\n*bool*::\n    a boolean value, yes/true or no/false\n\n*str*::\n    a string, some freeform text\n\n*regex*::\n    as a string but the set commands will complain if the entered text\n    is not a valid regex\n\n*coord*::\n    a line, column pair (separated by a comma)\n    Cannot be used with `declare-option`\n\n*<type>-list*::\n    a list, elements are specified as separate arguments to the command.\n\n    `set -add` appends the new element to the list. +\n    `set -remove` removes each given element from the list. +\n\n    Only `int-list` and `str-list` options can be created with\n    `declare-option`.\n\n*range-specs*::\n    a timestamp (like `%val{timestamp}`,\n    see <<expansions#value-expansions,`:doc expansions value-expansions`>>)\n    followed by a list of range descriptors.\n\n    Each range descriptor must use the syntax `a.b,c.d|string` or\n    `a.b+length|string`, with:\n\n        * _a_ is the line containing the first character\n\n        * _b_ is the number of bytes from the start of the line to the\n        first byte of the first character\n\n        * _c_ is the line containing the last character\n\n        * _d_ is the number of bytes from the start of the line to the\n          first byte of the last character\n\n        * _length_ is the length of the range in bytes, if 0 the range\n          is empty, but still valid.\n\n        * _string_ is an arbitrary string which is associated with\n          the range. Any `|` or `\\` characters must be escaped as `\\|` or `\\\\`.\n\n    All numeric fields are 1-based.\n\n    When the command `update-option` is used on an option of this type,\n    its ranges get updated according to all the buffer modifications\n    that happened since its timestamp.\n\n    `set -add` appends the new pairs to the list. +\n    `set -remove` removes the given pairs from the list. +\n\n    See <<highlighters#specs-highlighters,`:doc highlighters specs-highlighters`>>)\n\n*line-specs*::\n    a list of a line number and a corresponding flag (`<line>|<flag\n    text>`), except for the first element which is just the timestamp\n    of the buffer. When `update-option` is used on an option of this\n    type, its lines get updated according to all the buffer modifications\n    that happened since its timestamp.\n    See <<highlighters#specs-highlighters,`:doc highlighters specs-highlighters`>>)\n\n    `set -add` appends the new specs to the list. +\n    `set -remove` removes the given specs from the list. +\n\n    Any `|` or `\\` characters that occur within `<flag text>` must be\n    escaped as `\\|` or `\\\\`.\n\n*completions*::\n    a list of `<text>|<select cmd>|<menu text>` candidates,\n    except for the first element which follows the\n    `<line>.<column>[+<length>]@<timestamp>` format to define where the\n    completion apply in the buffer.\n    Any `|` or `\\` characters that occur within the `<text>`,\n    `<select cmd>`, or `<menu text>` fields should be escaped as `\\|`\n    or `\\\\`.\n\n    Options of this type are are meant to be added to the `completers`\n    option to provide insert mode completion. Candidates are shown if the\n    text typed by the user (between `<line>.<column>` and the cursor) is a\n    subsequence of `<text>`.\n\n    For each remaining candidate, the completion menu displays\n    `<text>`, followed by `<menu text>`, which is a Markup string (see\n    <<faces#markup-strings,`:doc faces markup-strings`>>).\n\n    As the user selects items from the completion menu, the text they typed\n    will be replaced with `<text>`, and the Kakoune command in\n    `<select cmd>` is executed. The common use case is to display element\n    specific documentation.\n\n    `set -add` adds given completions to the list. +\n    `set -remove` removes given completions from the list. +\n\n*enum(value1|value2|...)*::\n    an enum, taking one of the given values\n    Cannot be used with `declare-option`\n\n*flags(value1|value2|...)*::\n    a set of flags, taking a combination of the given values joined by a\n    '|' character.\n\n    `set -add` adds the given flags to the combination. +\n    `set -remove` removes the given flags to the combination. +\n\n    Cannot be used with `declare-option`\n\n*<type>-to-<type>-map*::\n    a list of `key=value` pairs.\n\n    `set -add` adds the given pair to the hashmap or replace an already\n    existing key. +\n    `set -remove` removes the given pair from the hashmap, if only the\n    key is provided it removes that entry regardless of the associated\n    value. +\n\n    Only `str-to-str-map` options can be created with `declare-option`.\n\n== Builtin options\n\n*tabstop* `int`::\n    _default_ 8 +\n    width of a tab character\n\n*indentwidth* `int`::\n    _default_ 4 +\n    width (in spaces) used for indentation, 0 means a tab character\n\n*scrolloff* `coord`::\n    _default_ 0,0 +\n    number of lines, columns to keep visible around the cursor when\n    scrolling\n\n*eolformat* `enum(lf|crlf)`::\n    _default_ lf +\n    the format of end of lines when writing a buffer, this is autodetected\n    on load; values of this option assigned to the `window` scope are\n    ignored\n\n*finaleol* `enum(present|missing|ifnotempty)`::\n    _default_ present +\n    Whether to drop the end-of-line of the last line the buffer when writing\n    to the filesystem, this is autodetected on load; values of this option\n    assigned to the `window` scope are ignored.\n\n    When set to `ifnotempty` the final end-of-line will only be written if\n    the buffer has a single empty line.\n\n\n*BOM* `enum(none|utf8)`::\n    _default_ none +\n    define if the file should be written with a unicode byte order mark;\n    values of this option assigned to the `window` scope are ignored\n\n*readonly* `bool`::\n    _default_ false +\n    prevent modifications from being saved to disk, all buffers if set\n    to `true` in the `global` scope, or current buffer if set in the\n    `buffer` scope; values of this option assigned to the `window`\n    scope are ignored\n\n*incsearch* `bool`::\n    _default_ true +\n    execute search as it is typed\n\n*aligntab* `bool`::\n    _default_ false +\n    use tabs for alignment command\n\n*autoinfo* `flags(command|onkey|normal)`::\n    _default_ command|onkey +\n    display automatic information box in the enabled contexts\n\n*autocomplete* `flags(insert|prompt)`::\n    _default_ insert|prompt +\n    automatically display possible completions in the enabled modes.\n\n*ignored_files* `regex`::\n    filenames matching this regex won't be considered as candidates\n    on filename completion (except if the text being completed already\n    matches it)\n\n*disabled_hooks* `regex`::\n    hooks whose group matches this regex won't be executed. For example\n    indentation hooks can be disabled with `.*-indent`.\n    (See <<hooks#disabling-hooks,`:doc hooks`>>)\n\n*filetype* `str`::\n    arbitrary string defining the type of the file. Filetype dependent\n    actions should hook on this option changing for activation/deactivation\n\n*path* `str-list`::\n    _default_ ./ %/ /usr/include +\n    directories to search for *gf* command and filenames completion\n    `%/` represents the current buffer directory\n\n*completers* `completer-list`::\n    _default_ filename word=all +\n    completion engines to use for insert mode completion (they are tried\n    in order until one generates candidates). Existing completers are:\n\n    *word=all*, *word=buffer*:::\n        which complete using words in all buffers (*word=all*)\n        or only the current one (*word=buffer*)\n\n    *filename*:::\n        which tries to detect when a filename is being entered and\n        provides completion based on local filesystem\n\n    *line=all*, *line=buffer*:::\n        which complete using lines in all buffers (*line=all*)\n        or only the current one (*line=buffer*)\n\n    *option=<opt-name>*:::\n        where *opt-name* is an option of type 'completions' whose\n        contents will be used\n\n*static_words* `str-list`::\n    list of words that are always added to completion candidates\n    when completing words in insert mode\n\n*extra_word_chars* `codepoint-list`::\n    _default_ _ +\n    a list of all additional codepoints that should be considered\n    part of a word for the purposes of the `w`, `b`, and `e` commands\n    (See <<keys#movement,`:doc keys movement`>>),\n    the `word=buffer` and `word=all` completers\n    (See `completers` above),\n    \"word\" objects\n    (See <<keys#object-selection,`:doc keys object-selection`>>), etc.\n    This must be set on the buffer, not the window,\n    for word completion to offer words containing these codepoints.\n\n*matching_pairs* `codepoint-list`::\n    _default_ ( ) { } [ ] < > +\n    a list of codepoints that are to be treated as matching pairs\n    for the *m* command.\n\n*autoreload* `enum(yes|no|ask)`::\n    _default_ ask +\n    auto reload the buffers when an external modification is detected\n\n*writemethod* `enum(overwrite|replace)`::\n    _default_ overwrite +\n    method used to write buffers to file, `overwrite` will open the\n    existing file and write on top of the previous data, `replace`\n    will open a temporary file next to the target file, write it and\n    then rename it to the target file.\n\n*debug* `flags(hooks|shell|profile|keys|commands)`::\n    dump various debug information in the `\\*debug*` buffer\n\n*idle_timeout* `int`::\n    _default_ 50 +\n    timeout, in milliseconds, with no user input that will trigger the\n    *PromptIdle*, *InsertIdle* and *NormalIdle* hooks, and autocompletion.\n\n*fs_check_timeout* `int`::\n    _default_ 500 +\n    timeout, in milliseconds, between checks in normal mode of modifications\n    of the file associated with the current buffer on the filesystem.\n\n*modelinefmt* `string`::\n    A format string used to generate the mode line, that string is\n    first expanded as a command line would be (expanding '%...{...}'\n    strings), then markup tags are applied (see\n    <<faces#markup-strings,`:doc faces markup-strings`>>)\n    Two special atoms are available as markup:\n\n        *`{{mode_info}}`*:::\n            Information about the current mode, such as `insert 3 sel` or\n            `prompt`. The faces used are StatusLineMode, StatusLineInfo,\n            and StatusLineValue.\n\n        *`{{context_info}}`*:::\n            Information such as `[+][recording (@)][no-hooks][new file][fifo]`,\n            in face Information.\n\n    The default value is '%val{bufname} %val{cursor_line}:%val{cursor_char_column} {{context_info}} {{mode_info}} - %val{client}@[%val{session}]'\n\n*ui_options* `str-to-str-map`::\n    a list of `key=value` pairs that are forwarded to the user\n    interface implementation. The Terminal UI supports the following options:\n\n        *terminal_set_title*:::\n            if *yes* or *true*, the terminal emulator title will\n            be changed\n\n        *terminal_title*:::\n            if set, the terminal title will be set to this string\n            else it will be derived from the modeline.\n\n        *terminal_status_on_top*:::\n            if *yes*, or *true* the status line will be placed\n            at the top of the terminal rather than at the bottom\n\n        *terminal_assistant*:::\n            specify the nice assistant displayed in info boxes,\n            can be *clippy* (the default), *cat*, *dilbert* or *none*\n\n        *terminal_enable_mouse*:::\n            boolean option that enables mouse support\n\n        *terminal_shift_function_key*:::\n            Function key from which shifted function key start, if the\n            terminal sends F13 for <s-F1>, this should be set to 12.\n\n        *terminal_padding_char*:::\n            character used to indicate the area out of the displayed buffer\n            (defaults to '~')\n\n        *terminal_padding_fill*:::\n            if *yes* or *true*, fill the padding area with the padding character\n            instead of displaying a single character at the beginning of the\n            padding line (defaults to *false*)\n\n        *terminal_synchronized*:::\n            if *yes* or *true*, emit synchronized output escape sequences and\n            reduce terminal output with sequences that could trigger flickering\n            if unsynchronized (defaults to *false*)\n\n        *terminal_info_max_width*:::\n            set the maximum allowable width of an info box. set to zero for\n            no limit.\n\n        *terminal_cursor_native*:::\n            if *yes* or *true*, use native terminal cursor visibility control\n            instead of Kakoune's cursor management (defaults to *false*)\n\n[[startup-info]]\n*startup_info_version* `int`::\n    _default_ 0 +\n    Controls which messages will be displayed in the startup info box, only messages\n    relating to a Kakoune version greater than this value will be displayed. Versions\n    are written as a single number: Like `20180413` for version `2018.04.13`\n\n== Current values\n\nThe current value for an option can be viewed using\n<<expansions#option-expansions, `:doc expansions option-expansions`>>.\n\nFor example, the current value of the `BOM` option can be displayed in the\nstatus line using the `echo` command:\n\n--------------\necho %opt{BOM}\n--------------\n\nThe current values for all options can be dumped to the `\\*debug*` buffer using\nthe following command:\n\n-------------\ndebug options\n-------------\n"
  },
  {
    "path": "doc/pages/regex.asciidoc",
    "content": "= Regex\n\n== Regex syntax\n\nKakoune's regex syntax is inspired by ECMAScript, as defined by the\nECMA-262 standard (see <<regex#compatibility,:doc regex compatibility>>).\n\nKakoune's regex always runs on Unicode codepoint sequences, not on bytes.\n\n== Literals\n\nEvery character except the syntax characters `\\^$.*+?[]{}|().` match\nthemselves. Syntax characters can be escaped with a backslash so that\n`\\$` will match a literal `$`, and `\\\\` will match a literal `\\`.\n\nSome literals are available as escape sequences:\n\n* `\\f` matches the form feed character.\n* `\\n` matches the newline character.\n* `\\r` matches the carriage return character.\n* `\\t` matches the tabulation character.\n* `\\v` matches the vertical tabulation character.\n* `\\0` matches the null character.\n* `\\cX` matches the control-`X` character (`X` can be in `[A-Za-z]`).\n* `\\xXX` matches the character whose codepoint is `XX` (in hexadecimal).\n* `\\uXXXXXX` matches the character whose codepoint is `XXXXXX` (in hexadecimal).\n\n== Character classes\n\nThe `[` character introduces a character class, matching one character\nfrom a set of characters.\n\nA character class contains a list of literals, character ranges,\nand character class escapes surrounded by `[` and `]`.\n\nIf the first character inside a character class is `^`, then the character\nclass is negated, meaning that it matches every character not specified\nin the character class.\n\nLiterals match themselves, including syntax characters, so `^`\ndoes not need to be escaped in a character class. `[\\*+]` matches both\nthe `\\*` character and the `+` character. Literal escape sequences are\nsupported, so `[\\n\\r]` matches both the newline and carriage return\ncharacters.\n\nThe `]` character needs to be escaped for it to match a literal `]`\ninstead of closing the character class.\n\nCharacter ranges are written as `<start character>-<end character>`, so\n`[A-Z]` matches all uppercase basic letters. `[A-Z0-9]` will match all\nuppercase basic letters and all basic digits.\n\nThe `-` characters in a character class that are not specifying a\nrange are treated as literal `-`, so `[A-Z-+]` matches all upper case\ncharacters, the `-` character, and the `+` character.\n\nSupported character class escapes are:\n\n* `\\d` which matches digits 0-9.\n* `\\w` which matches word characters A-Z, a-z, 0-9 and underscore\n    (ignoring the `extra_word_chars` option).\n* `\\s` which matches all Unicode whitespace characters.\n* `\\h` which matches whitespace except Vertical Tab and line-breaks.\n\nUsing an upper case letter instead of a lower case one will negate\nthe character class. For example, `\\D` will match every non-digit\ncharacter.\n\nCharacter class escapes can be used outside of a character class, `\\d`\nis equivalent to `[\\d]`.\n\n== Any character\n\n`.` matches any character, including newlines, by default.\n(see <<regex#modifiers,:doc regex modifiers>> on how to change it)\n\n== Groups\n\nRegex atoms can be grouped using `(` and `)` or `(?:` and `)`. If `(` is\nused, the group will be a capturing group, which means the positions from\nthe subject strings that matched between `(` and `)` will be recorded.\n\nCapture groups are numbered starting at 1. They are numbered in the\norder of appearance of their `(` in the regex. A special capture group\n0 is for the whole sequence that matched.\n\n* `(?:` introduces a non capturing group, which will not record the\nmatching positions.\n\n* `(?<name>` introduces a named capturing group, which, in addition to\nbeing referred by number, can be, in certain contexts, referred by the\ngiven name.\n\n== Alternations\n\nThe `|` character introduces an alternation, which will either match\nits left-hand side, or its right-hand side (preferring the left-hand side)\n\nFor example, `foo|bar` matches either `foo` or `bar`, `foo(bar|baz|qux)`\nmatches `foo` followed by either `bar`, `baz` or `qux`.\n\n== Quantifier\n\nLiterals, character classes, any characters, and groups can be followed\nby a quantifier, which specifies the number of times they can match.\n\n* `?` matches zero, or one time.\n* `*` matches zero or more times.\n* `+` matches one or more times.\n* `{n}` matches exactly `n` times.\n* `{n,}` matches `n` or more times.\n* `{n,m}` matches `n` to `m` times.\n* `{,m}` matches zero to `m` times.\n\nBy default, quantifiers are *greedy*, which means they will prefer to\nmatch more characters if possible. Suffixing a quantifier with `?` will\nmake it non-greedy, meaning it will prefer to match as few characters\nas possible.\n\n== Zero width assertions\n\nAssertions do not consume any character, but they will prevent the regex\nfrom matching if not fulfilled.\n\n* `^` matches at the start of a line; that is, just after a newline\n      character, or at the subject's beginning (unless it is specified\n      that the subject's beginning is not a start of line).\n* `$` matches at the end of a line; that is, just before a newline, or\n      at the subject end (unless it is specified that the subject's end\n      is not an end of line).\n* `\\b` matches at a word boundary; which is to say that between the\n       previous character and the current character, one is a word\n       character, and the other is not.\n* `\\B` matches at a non-word boundary; meaning, when both the previous\n       character and the current character are word characters, or both\n       are not.\n* `\\A` matches at the subject string's beginning.\n* `\\z` matches at the subject string's end.\n* `\\K` matches anything, and resets the start position of capture group\n       0 to the current position.\n\nMore complex assertions can be expressed with lookarounds:\n\n* `(?=...)` is a lookahead; it will match if its content matches the\n            text following the current position.\n* `(?!...)` is a negative lookahead; it will match if its content does\n            not match the text following the current position.\n* `(?<=...)` is a lookbehind; it will match if its content matches\n             the text preceding the current position.\n* `(?<!...)` is a negative lookbehind; it will match if its content does\n             not match the text preceding the current position.\n\nFor performance reasons, lookaround contents must be a sequence of\nliterals, character classes, or any character (`.`); quantifiers are not\nsupported.\n\nFor example, `(?<!bar)(?=foo).` will match any character which is not\npreceded by `bar` and where `foo` matches from the current position\n(which means the character has to be an `f`).\n\n== Modifiers\n\nSome modifiers can control the matching behavior of the atoms following\nthem:\n\n* `(?i)` starts case-insensitive matching.\n* `(?I)` starts case-sensitive matching (default).\n* `(?s)` allows `.` to match newlines (default).\n* `(?S)` prevents `.` from matching newlines.\n\n== Quoting\n\n`\\Q` will start a quoted sequence, where every character is treated as\na literal. That quoted sequence will continue until either the end of\nthe regex, or the appearance of `\\E`.\n\nFor example, `.\\Q.^$\\E$` will match any character followed by the\nliteral string `.^$`, followed by an end of line.\n\n== Compatibility\n\nKakoune's syntax tries to follow the ECMAScript regex syntax, as defined\nby <https://www.ecma-international.org/ecma-262/8.0/>; some divergence\nexists for ease of use, or performance reasons:\n\n* Lookarounds are not arbitrary, but lookbehind is supported.\n* `\\K`, `\\Q..\\E`, `\\A`, `\\h` and `\\z` are added.\n* Stricter handling of escaping, as we introduce additional escapes;\n  identity escapes like `\\X` with `X` being a non-special character\n  are not accepted, to avoid confusions between `\\h` meaning literal\n  `h` in ECMAScript, and horizontal blank in Kakoune.\n* `\\uXXXXXX` uses 6 digits to cover all of Unicode, instead of relying\n  on ECMAScript UTF-16 surrogate pairs with 4 digits.\n"
  },
  {
    "path": "doc/pages/registers.asciidoc",
    "content": "= Registers\n\n== Description\n\nRegisters are named lists of text -instead of simply text- in order to interact\nwell with multiselection. They are used for various purposes, like storing\nyanked text, the locations of selections in a buffer, or groups captured by a\nregular expression.\n\n== Interacting\n\n*<c-r><c>*::\n    when in insert mode or in a prompt, insert the value stored in the\n    *c* register (single character)\n\n*\"<c>*::\n    in normal mode, select the *<c>* register (single character)\n\n== Alternate names\n\nNon alphanumeric registers have an alternative name that can be used\nin contexts where only alphanumeric identifiers are possible.\n\n== Default registers\n\nAll normal-mode commands using a register default to a specific one if not specified:\n\n*\"* (dquote)::\n    default delete / copy / paste / replace register, used by:\n    *c*, *d*, *y*, *p*, *<a-p>*, *<P>*, *<a-P>*, *R* and *<a-R>*\n    (see <<keys#changes, `:doc keys changes`>>)\n\n*/* (slash)::\n    default search / regex register, used by:\n    */*, *<a-/>*, *?*, *<a-?>*, *n*, *<a-n>*, *N*, *<a-N>*, ***, *<a-***>*,\n    *s*, *S*, *<a-k>* and *<a-K>*\n    (see <<keys#searching, `:doc keys searching`>>).\n    This is a prompt history register, holding the last 100 commands entered\n    at an interactive regex prompt.\n\n*@* (arobase)::\n    default macro register, used by:\n    *q* and *Q*\n    (see <<keys#macros, `:doc keys macros`>>)\n\n*^* (caret)::\n    default mark register, used by:\n    *z*, *<a-z>*, *Z* and *<a-Z>*\n    (see <<keys#marks, `:doc keys marks`>>\n    and <<registers#marks, `:doc registers marks`>>)\n\n*|* (pipe)::\n    default shell command register, used by commands that spawn a subshell:\n    *|*, *<a-|>*, *!* and *<a-!>*\n    (see <<keys#changes-through-external-programs, `:doc keys changes-through-external-programs`>>).\n    This is a prompt history register, holding the last 100 commands entered\n    at interactive shell command prompts, except for commands starting with\n    a space.\n\n== Special registers\n\nSome registers are not general purposes, they cannot be written to, but they\ncontain some special data\n\n*%* (percent)::\n    current buffer name\n\n*.* (dot)::\n    current selection contents\n\n*#* (hash)::\n    selection indices (first selection has 1, second has 2, ...)\n\n*_* (underscore)::\n    null register, always empty\n\n*:* (colon)::\n    prompt history register holding the last 100 commands entered at the\n    interactive prompt, except for commands starting with a space.\n\n== Integer registers\n\nRegisters *1* to *9* hold the grouped sub-matches of the regular\nexpression used to make the last selection. Example: applying the\nfollowing regular expression to the date of the day would put the day of\nthe week in register *1*, the month in register *2*, and the day of the\nmonth in register *3*, but select the entire date:\n\n--------------------\n(\\w+) (\\w+) (\\d+) .+\n--------------------\n\n== Marks\n\nWhen a register is used to store a set of selections with the *Z* key (see\n<<keys#marks, `:doc keys marks`>>), the selections are stored as a list of\nspans, in a format similar to `%val{selections_desc}` (see\n<<expansions#value-expansions, `:doc expansions value-expansions`>>). However,\nthe very first item of the list is of the form:\n\n------------------------------------------\n<buffer name>@<timestamp>@<main sel index>\n------------------------------------------\n\nwith:\n\n*buffer name*::\n    `%val{buffile}` of the buffer selections relate to\n\n*timestamp*::\n    `%val{timestamp}` at which the selection applies to\n\n*main sel index*::\n    0-based index of the main selection\n"
  },
  {
    "path": "doc/pages/scopes.asciidoc",
    "content": "= Scopes\n\n== Description\n\nScopes are groups in which a particular Kakoune object can have different\nvalues depending on the group the value was declared in.\n\nThese scoped objects are:\n\n- aliases (See <<commands#,`:doc commands`>>)\n- faces (See <<faces#,`:doc faces`>>)\n- highlighters (See <<highlighters#,`:doc highlighters`>>)\n- hooks (See <<hooks#,`:doc hooks`>>)\n- keymaps (See <<mapping#,`:doc mapping`>>)\n- options (See <<options#,`:doc options`>>)\n\n== Names and hierarchy\n\nScopes are named as follows:\n\n*window*::\n    context linked to the window displaying a buffer.\n\n    In Kakoune, the concept of a *window* must not be confused with\n    the concept of a window at the OS level.\n    In other terms, a window is *not* a client (like a terminal or GUI)\n    but one of many 'views' into a buffer.\n\n    There is a N:1 relationship between windows and buffers; once a\n    window is linked to a buffer, the window's buffer never changes.\n    Windows store a set of selections and the scroll position.\n\n*buffer*::\n    context linked directly to the buffer\n\n*global*::\n    global context linked to the instance of Kakoune\n\n*local*::\n    A local scope is inserted by each *evaluate-commands* invocations\n    for its duration. Nested *evaluate-commands* each inject a new\n    local scope whose parent is the previous local scope. User declared\n    commands defined with *define-command* also introduce a local scope\n    while executing.\n\n    A local scope is intended for temporarily overwriting some scoped\n    value, such as an option or an alias.\n\n\nThe following order of priority applies to the above scopes:\n\n-----------------------------------\nlocal ]> window ]> buffer ]> global\n-----------------------------------\n\nThe above priority line implies that objects can have individual values\nthat will be resolved first in the *local* scope (if it exists), then the\n*window* scope, then in the *buffer* scope, and finally in the *global*\nscope.\n\nNormally, the *buffer* scope keyword means the scope associated with the\ncurrently active buffer, but it's possible to specify any existing buffer by\nadding an `=` and the value of `%val{buffile}` for that buffer\n(See <<expansions#value-expansions,`:doc expansions value-expansions`>>).\nFor example, to set the `indentwidth` option for the `/etc/fstab` buffer::\n\n----\nset-option buffer=/etc/fstab indentwidth 8\n----\n\nThe `set-option` and `unset-option` commands also accept *current* as \na valid scope name. It refers to the narrowest scope the option is set in.\n\n== Uses\n\nThe scope paradigm is very useful as it allows the user to customize the\nbehavior of the editor without modifying the configuration globally, as\nis the case with other editors who only have a single *global* scope by\ndefault.\n\nExamples:\n\n*filetype*::\n    A single buffer opened in two separate windows can have different\n    filetypes declared in the *window* scope with 'set-option'.\n    (See <<options#,`:doc options`>>)\n\n*status line*::\n    All the buffers of the current session can have the same information\n    displayed in the status line, except for a specific buffer (the\n    'modelinefmt' option can be declared in the *global* scope, and\n    customized in the *buffer* scope with 'set-option'.\n    (See <<options#,`:doc options`>>)\n\n== Execution context\n\nSome commands work in a specific context that might exclude one or\nseveral scopes altogether, consequently ignoring some values of a given\nobject.\n\nExample: the *window* scope is never considered when resolving the\nvalues of options when writing a buffer (e.g. 'BOM', 'eolformat').\n"
  },
  {
    "path": "doc/writing_scripts.asciidoc",
    "content": "Writing kak scripts\n===================\n\nInteraction with external tools from a Kakoune session is supported\nthrough the use of scripts. Once loaded by the editor (either\nautomatically or manually), they allow extending the functionalities\nprovided by default through commands and hooks.\n\nTheir implementation should be kept as simple as possible, as they are\nnot meant to be generic tools themselves but a mere API to actual\nsoftware.\n\n---------------------------------------------------\n\t                                       +------+\n\t                                  +--> | tmux |\n\t                                  |    +------+\n\t                                  |\n\t+---------+                       |    +------+\n\t| Kakoune | <------->  scripts  +-+--> | X11  |\n\t+---------+                       |    +------+\n\t                                  |\n\t                                  |    +------+\n\t                                  +--> | ...  |\n\t                                       +------+\n---------------------------------------------------\n\nDependencies\n------------\n\nThe amount of dependencies of a given script should be kept to a minimum\nfor practicality reasons, and have to be reasonable and expected\nconsidering the purpose of the script itself.\n\nExamples:\n\n* the `clang.kak` script provides with code completion using the `clang`\n  compiler\n\n* the `tmux.kak` script provides with terminal splitting using the\n  `tmux` multiplexer\n\n* the `ctags.kak` script provides with symbol lookups using the\n  `readtags` utility provided by some `ctags` implementations\n\nNaming convention\n-----------------\n\nAll options and commands declared in a Kakoune script have to be\nprefixed with the name of the script, or a one word description of the\npurpose of the script.\n\nExamples:\n\n* in `tmux.kak`: command `tmux-new-window`\n\n* in `comment.kak`: option `comment_line`\n\nThe following conventions apply as well:\n\n* *options*: if a separator is needed to separate a multiple word option\n  name, an underscore should be used to allow shell scopes to use them\n\n* *commands*: if a separator is needed, a hyphen is usually used to\n  differentiate a command name from an option's\n\nDocumentation\n-------------\n\nNon-hidden commands and options should always be declared with a documentation\nstring, so that their purpose is clearly described whenever completed upon\ninteractively from the prompt.\n\nPOSIX shell\n-----------\n\nShell expansions are a useful tool to interact with an external utility,\nand the shell code that they contain should be as portable as possible. As\nsuch, scripts that rely on those expansions have to be implemented with\nPOSIX in mind, most shells follow this standard nowadays which somewhat\nguarantees that the script will be portable across the most common systems.\n\nCommon shell patterns\n---------------------\n\nPrinting variables\n~~~~~~~~~~~~~~~~~~\n\nIn order to print a string that contains a variable expansion, prefer\n`printf` to `echo`, as the latter is implementation defined and may\ninterpret some characters differently depending on the shell (e.g.\nflags, backslashes).\n\n----------------------------------\n\tprintf %s\\\\n \"${var}\"\n\tprintf \"value: %s\\\\n\" \"${var}\"\n----------------------------------\n\nThe following won't cause any issues, as the string to print doesn't\ncontain ambiguous characters:\n\n-------------------------------------\n\techo \"set global scrolloff 999,0\"\n-------------------------------------\n\nFor more information about portability issues related to `echo`, refer to https://www.etalabs.net/sh_tricks.html[Rich's sh tricks].\n\nVariable base name\n~~~~~~~~~~~~~~~~~~\n\nReplace `$(basename \"${var}\")` with `\"${var##*/}\"`.\n\nTesting\n~~~~~~~\n\nThe `[[` keyword is provided by `bash`, and should be replaced with `[`.\n\nStandard error redirection\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nRedirecting both standard and error streams is simplified in the `bash`\nshell with the `&>` operator, however this syntax is not portable and\nhas to be replaced with the following: `>/dev/null 2>&1`.\n\nRegular expression\n~~~~~~~~~~~~~~~~~~\n\nThe `bash` shell provides with a `[[` keyword that supports the `=~`\noperator to match a regular expression against a variable. This\nfunctionality can be implemented with the `expr` utility:\n\n* `expr \"${var}\" : '[a-z]*' >/dev/null` returns successfully when the\n  variable is empty or only contains lowercase characters, otherwise a\n  non-zero exit code is returned\n\n* `expr \"${var}\" : '\\([a-z]*\\)'` prints the variable when empty or\n  only contains lowercase characters\n\nNote that the regular expression matches the whole string, using the `^`\nand `$` anchors is an undefined behavior.\n\nRunning a process in the background\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn order to get a process running in the background without having it\nquit when the shell scope that spawns it terminates, use the following\nsyntax:\n\n--------------------------------\n\t{\n\t    command\n\t} </dev/null >/dev/null 2>&1 &\n--------------------------------\n"
  },
  {
    "path": "gdb/kakoune.py",
    "content": "import gdb.printing\n\nclass ArrayIterator:\n    def __init__(self, data, count):\n        self.data = data\n        self.count = count\n        self.index = 0\n\n    def __iter__(self):\n        return self\n\n    def next(self):\n        if self.index == self.count:\n            raise StopIteration\n\n        index = self.index\n        self.index = self.index + 1\n        return ('[%d]' % index, (self.data + index).dereference())\n\n    def __next__(self):\n        return self.next()\n\n\nclass ArrayView:\n    \"\"\"Print a ArrayView\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def display_hint(self):\n        return 'array'\n\n    def children(self):\n        return ArrayIterator(self.val['m_pointer'], self.val['m_size'])\n\n    def to_string(self):\n        type = self.val.type.template_argument(0).unqualified().strip_typedefs()\n        return \"ArrayView<%s>\" % (type)\n\n\nclass LineAndColumn:\n    \"\"\"Print a LineAndColumn\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        value_type = self.val.type.unqualified()\n        return \"%s(%s, %s)\" % (value_type, self.val['line'],\n                               self.val['column'])\n\n\nclass BufferCoordAndTarget:\n    \"\"\"Print a BufferCoordAndTarget\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        value_type = self.val.type.unqualified()\n        return \"%s(%s, %s, %s)\" % (value_type, self.val['line'],\n                                   self.val['column'], self.val['target'])\n\n\nclass BufferIterator:\n    \"\"\" Print a BufferIterator\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        line = self.val['m_coord']['line']\n        column = self.val['m_coord']['column']\n        if self.val['m_buffer']['m_ptr'] != 0:\n            buf = self.val['m_buffer']['m_ptr'].dereference()['m_name']\n            return \"buffer<%s>@(%s, %s)\" % (buf, line, column)\n        else:\n            return \"buffer<none>@(%s, %s)\" % (line, column)\n\n\nclass String:\n    \"\"\" Print a String\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        data_u = self.val[\"m_data\"][\"u\"]\n        long = data_u[\"l\"]\n        short = data_u[\"s\"]\n        if (long[\"mode\"] & long[\"active_mask\"]) != 0:\n            ptr = long[\"ptr\"]\n            len = long[\"size\"]\n        else:\n            ptr = short[\"string\"]\n            short_capacity = short\n            len = short[\"capacity\"] - short[\"remaining_size\"]\n        return \"\\\"%s\\\"\" % (ptr.string(\"utf-8\", \"ignore\", len))\n\n\nclass StringView:\n    \"\"\" Print a StringView\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        len = self.val['m_length']['m_value']\n        return \"\\\"%s\\\"\" % (self.val['m_data'].string(\"utf-8\", \"ignore\", len))\n\n\nclass StringDataPtr:\n    \"\"\" Print a RefPtr<StringData>\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        ptr = self.val['m_ptr']\n        str_type = gdb.lookup_type(\"char\").pointer()\n        len = ptr.dereference()['length']\n        refcount = ptr.dereference()['refcount']\n        content = (ptr + 1).cast(str_type).string(\"utf-8\", \"ignore\", len)\n        return \"\\\"%s\\\" (ref:%d)\" % (content.replace(\"\\n\", \"\\\\n\"), refcount)\n\n\nclass RefPtr:\n    \"\"\" Print a RefPtr\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        ptr = self.val['m_ptr']\n        return \"\\\"refptr %s\\\"\" % (ptr)\n\n\nclass Option:\n    \"\"\" Print a Option\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        return self.val[\"m_value\"]\n\n\nclass CharCount:\n    \"\"\"Print a CharCount\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        return self.val[\"m_value\"]\n\n\nclass ColumnCount:\n    \"\"\"Print a ColumnCount\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        return self.val[\"m_value\"]\n\n\nclass ByteCount:\n    \"\"\"Print a ByteCount\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        return self.val[\"m_value\"]\n\n\nclass LineCount:\n    \"\"\"Print a LineCount\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        return self.val[\"m_value\"]\n\n\nclass Color:\n    \"\"\"Print a Color\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        named_color = gdb.lookup_type(\"Kakoune::Color::NamedColor\")\n        if self.val[\"color\"] == named_color[\"Kakoune::Color::RGB\"].enumval:\n            return \"%s #%02x%02x%02x\" % (self.val[\"color\"], self.val[\"r\"],\n                                         self.val[\"g\"], self.val[\"b\"])\n        else:\n            return self.val[\"color\"]\n\nclass Regex:\n    \"\"\"Print a Regex\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def to_string(self):\n        return \"regex%s\" % (self.val[\"m_str\"])\n\n\ndef build_pretty_printer():\n    pp = gdb.printing.RegexpCollectionPrettyPrinter(\"kakoune\")\n    pp.add_printer('ArrayView', '^Kakoune::(Const)?ArrayView<.*>$', ArrayView)\n    pp.add_printer('LineAndColumn', '^Kakoune::(Buffer|Display)Coord$', LineAndColumn)\n    pp.add_printer('BufferCoordAndTarget', '^Kakoune::BufferCoordAndTarget$', BufferCoordAndTarget)\n    pp.add_printer('BufferIterator', '^Kakoune::BufferIterator$', BufferIterator)\n    pp.add_printer('String', '^Kakoune::String$', String)\n    pp.add_printer('StringView', '^Kakoune::(StringView|SharedString)$', StringView)\n    pp.add_printer('StringDataPtr', '^Kakoune::StringDataPtr$', StringDataPtr)\n    pp.add_printer('StringDataPtr', '^Kakoune::RefPtr<Kakoune::StringData,.*>$', StringDataPtr)\n    pp.add_printer('RefPtr', '^Kakoune::RefPtr<.*>$',  RefPtr)\n    pp.add_printer('Option', '^Kakoune::Option$', Option)\n    pp.add_printer('LineCount', '^Kakoune::LineCount$', LineCount)\n    pp.add_printer('CharCount', '^Kakoune::CharCount$', CharCount)\n    pp.add_printer('ColumnCount', '^Kakoune::ColumnCount$', ColumnCount)\n    pp.add_printer('ByteCount', '^Kakoune::ByteCount$', ByteCount)\n    pp.add_printer('Color', '^Kakoune::Color$', Color)\n    pp.add_printer('Regex', '^Kakoune::Regex$', Regex)\n    return pp\n\nif __name__ == \"__main__\":\n    print(\"Adding kakoune pretty printers\")\n    gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printer())\n"
  },
  {
    "path": "rc/detection/editorconfig.kak",
    "content": "# http://editorconfig.org/#file-format-details\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](editorconfig) %{\n    set-option buffer filetype ini\n    set-option buffer static_words indent_style indent_size tab_width \\\n    end_of_line charset insert_final_newline trim_trailing_whitespace root \\\n    latin1 utf-8 utf-8-bom utf-16be utf-16le lf cr crlf unset space tab max_line_length\n}\n\ndefine-command editorconfig-load -params ..1 -docstring \"editorconfig-load [file]: set formatting behavior according to editorconfig\" %{\n    evaluate-commands %sh{\n        command -v editorconfig >/dev/null 2>&1 || { echo \"fail editorconfig could not be found\"; exit 1; }\n\n        file=\"${1:-$kak_buffile}\"\n        case $file in\n            /*) # $kak_buffile is a full path that starts with a '/'\n                printf %s\\\\n \"remove-hooks buffer editorconfig-hooks\"\n                editorconfig \"$file\" | awk -v file=\"$file\" -F= -- '\n                    $1 == \"indent_style\"             { indent_style = $2 }\n                    $1 == \"indent_size\"              { indent_size = $2 == \"tab\" ? 4 : $2 }\n                    $1 == \"tab_width\"                { tab_width = $2 }\n                    $1 == \"end_of_line\"              { end_of_line = $2 }\n                    $1 == \"insert_final_newline\"     { insert_final_newline = $2 }\n                    $1 == \"charset\"                  { charset = $2 }\n                    $1 == \"trim_trailing_whitespace\" { trim_trailing_whitespace = $2 }\n                    $1 == \"max_line_length\"          { max_line_length = $2 }\n\n                    END {\n                        if (indent_style == \"tab\") {\n                            print \"set-option buffer indentwidth 0\"\n                        }\n                        if (indent_style == \"space\") {\n                            print \"set-option buffer indentwidth \" indent_size\n                        }\n                        if (indent_size || tab_width) {\n                            print \"set-option buffer tabstop \" (tab_width ? tab_width : indent_size)\n                        }\n                        if (end_of_line == \"lf\" || end_of_line == \"crlf\") {\n                            print \"set-option buffer eolformat \" end_of_line\n                        }\n                        if (insert_final_newline == \"true\" || insert_final_newline == \"false\") {\n                            print \"set-option buffer finaleol \" (insert_final_newline == \"true\" ? \"present\" : \"missing\")\n                        }\n                        if (charset == \"utf-8-bom\") {\n                            print \"set-option buffer BOM utf8\"\n                        }\n                        if (trim_trailing_whitespace == \"true\") {\n                            print \"hook buffer BufWritePre \\\"\" file \"\\\" -group editorconfig-hooks %{ try %{ execute-keys -draft %{%s\\\\h+$<ret>d} } }\"\n                        }\n                        if (max_line_length && max_line_length != \"off\") {\n                            print \"set window autowrap_column \" max_line_length\n                            print \"autowrap-enable\"\n                            print \"add-highlighter window/ column %sh{ echo $((\" max_line_length \"+1)) } default,bright-black\"\n                        }\n                    }\n                ' ;;\n        esac\n    }\n}\ncomplete-command editorconfig-load file\n"
  },
  {
    "path": "rc/detection/file.kak",
    "content": "define-command -hidden file-detection %{ evaluate-commands %sh{\n    if [ -z \"${kak_opt_filetype}\" ]; then\n        mime=$(file -b --mime-type -L \"${kak_buffile}\")\n        mime=${mime%;*}\n        case \"${mime}\" in\n            application/*+xml) filetype=\"xml\" ;;\n            image/*+xml) filetype=\"xml\" ;; #SVG\n            message/rfc822) filetype=\"mail\" ;;\n            text/x-shellscript) filetype=\"sh\" ;;\n            text/x-script.*) filetype=\"${mime#text/x-script.}\" ;;\n            text/x-*) filetype=\"${mime#text/x-}\" ;;\n            text/plain) exit ;;\n            text/*)   filetype=\"${mime#text/}\" ;;\n            application/x-shellscript) filetype=\"sh\" ;;\n            application/x-*) filetype=\"${mime#application/x-}\" ;;\n            application/*) filetype=\"${mime#application/}\" ;;\n            *) exit ;;\n        esac\n        if [ -n \"${filetype}\" ]; then\n            printf \"set-option buffer filetype '%s'\\n\" \"${filetype}\"\n        fi\n    fi\n} }\n\nhook -group file-detection global BufOpenFile .* file-detection\nhook -group file-detection global BufWritePost .* file-detection\n"
  },
  {
    "path": "rc/detection/modeline.kak",
    "content": "##\n## modeline.kak by lenormf\n##\n\n## Currently supported modeline format: vim\n## Also supports kakoune options with a 'kak' or 'kakoune' prefix\n## Only a few options are supported, in order to prevent the\n## buffers from poking around the configuration too much\n\ndeclare-option -docstring \"amount of lines that will be checked at the beginning and the end of the buffer\" \\\n    int modelines 5\n\ndefine-command -hidden modeline-parse-impl %{\n    evaluate-commands %sh{\n        kakquote() { printf \"%s\" \"$*\" | sed \"s/'/''/g; 1s/^/'/; \\$s/\\$/'/\"; }\n\n        # Translate a vim option into the corresponding kakoune one\n        translate_opt_vim() {\n            local key=\"$1\"\n            local value=\"$2\"\n\n            case \"${key}\" in\n                so|scrolloff)\n                    key=\"scrolloff\";\n                    value=\"${value},${kak_opt_scrolloff##*,}\";;\n                siso|sidescrolloff)\n                    key=\"scrolloff\";\n                    value=\"${kak_opt_scrolloff%%,*},${value}\";;\n                ts|tabstop) key=\"tabstop\";;\n                sw|shiftwidth) key=\"indentwidth\";;\n                tw|textwidth) key=\"autowrap_column\";;\n                ff|fileformat)\n                    key=\"eolformat\";\n                    case \"${value}\" in\n                        unix) value=\"lf\";;\n                        dos) value=\"crlf\";;\n                        *)\n                            printf '%s\\n' \"Unsupported file format: ${value}\" >&2\n                            return;;\n                    esac\n                ;;\n                ft|filetype) key=\"filetype\";;\n                bomb)\n                    key=\"BOM\";\n                    value=\"utf8\";;\n                nobomb)\n                    key=\"BOM\";\n                    value=\"none\";;\n                spelllang|spl)\n                    key=\"spell_lang\";\n                    value=\"${value%%,*}\";;\n                *)\n                    printf '%s\\n' \"Unsupported vim variable: ${key}\" >&2\n                    return;;\n            esac\n\n            printf 'set-option buffer %s %s\\n' \"${key}\" \"$(kakquote \"${value}\")\"\n        }\n\n        # Pass a few whitelisted options to kakoune directly\n        translate_opt_kakoune() {\n            local readonly key=\"$1\"\n            local readonly value=\"$2\"\n\n            case \"${key}\" in\n                scrolloff|tabstop|indentwidth|autowrap_column|eolformat|filetype|BOM|spell_lang);;\n                *) printf 'echo -debug %s' \"$(kakquote \"Unsupported kakoune variable: ${key}\")\" \\\n                       | kak -p \"${kak_session}\"\n                   return;;\n            esac\n\n            printf 'set-option buffer %s %s\\n' \"${key}\" \"$(kakquote \"${value}\")\"\n        }\n\n        case \"${kak_selection}\" in\n            *vi:*|*vim:*) type_selection=\"vim\";;\n            *kak:*|*kakoune:*) type_selection=\"kakoune\";;\n            *)\n                printf 'fail %s\\n' \"$(kakquote \"Unsupported modeline format: ${kak_selection}\")\"\n                exit 1 ;;\n        esac\n\n        # The following subshell will keep the actual options of the modeline, and strip:\n        # - the text that leads the first option, according to the official vim modeline format\n        # - the trailing text after the last option, and an optional ':' sign before it\n        # It will also convert the ':' seperators beween the option=value pairs\n        # More info: http://vimdoc.sourceforge.net/htmldoc/options.html#modeline\n        printf %s \"${kak_selection}\" | sed                      \\\n                -e 's/^[^:]\\{1,\\}://'                           \\\n                -e 's/[ \\t]*set\\{0,1\\}[ \\t]\\([^:]*\\).*$/\\1/'    \\\n                -e 's/:[^a-zA-Z0-9_=-]*$//'                     \\\n                -e 's/:/ /g'                                    \\\n                | tr ' ' '\\n'                                   \\\n                | while read -r option; do\n            name_option=\"${option%%=*}\"\n            value_option=\"${option#*=}\"\n\n            if [ -z \"${option}\" ]; then\n                continue\n            fi\n\n            case \"${type_selection}\" in\n                vim) translate_opt_vim \"${name_option}\" \"${value_option}\";;\n                kakoune) translate_opt_kakoune \"${name_option}\" \"${value_option}\";;\n                *) exit 1;;\n            esac\n        done\n    }\n}\n\n# Add the following function to a hook on BufOpenFile to automatically parse modelines\n# Select the first and last `modelines` lines in the buffer, only keep modelines\n# ref. options.txt (in vim `:help options`) : 2 forms of modelines: \n#   [text]{white}{vi:|vim:|ex:}[white]{options}\n#   [text]{white}{vi:|vim:|Vim:|ex:}[white]se[t] {options}:[text]\ndefine-command modeline-parse -docstring \"Read and interpret vi-format modelines at the beginning/end of the buffer\" %{\n    try %{ evaluate-commands -draft -save-regs ^ %{\n        execute-keys -save-regs \"\" gk %opt{modelines} JK x Z\n        execute-keys gj %opt{modelines} KJ x <a-z> a\n        execute-keys s^\\S*?\\s+?\\w+:\\s?[^\\n]+<ret> x\n        evaluate-commands -draft -itersel modeline-parse-impl\n    } }\n}\n"
  },
  {
    "path": "rc/filetype/apl.kak",
    "content": "\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.(apl|aplf|aplo|apln|aplc|apli|dyalog) %{\n    set-option buffer filetype apl\n\n    set-option buffer matching_pairs ( ) { } [ ]\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=apl %|\n    require-module apl\n\n    hook window InsertChar \\n -group apl-insert apl-insert-on-new-line\n    hook window InsertChar \\n -group apl-indent apl-indent-on-new-line\n    hook window InsertChar [}⟩\\]] -group apl-indent apl-indent-on-closing\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window apl-.+ }\n|\n\nhook -group apl-highlight global WinSetOption filetype=apl %{\n    add-highlighter window/apl ref apl\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/apl }\n}\n\nprovide-module apl %¹\n\n# Highlighters & Completion\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/apl regions\nadd-highlighter shared/apl/code default-region group\nadd-highlighter shared/apl/comment region \"⍝\" \"$\" fill comment\nadd-highlighter shared/apl/string  region \"'\" \"'\" fill string\n\nadd-highlighter shared/apl/code/ regex \"[{}]\" 0:meta\nadd-highlighter shared/apl/code/ regex \"⋄\" 0:meta\nadd-highlighter shared/apl/code/ regex \"[\\[\\]]\" 0:magenta\nadd-highlighter shared/apl/code/ regex \"[()]\" 0:bright-black\nadd-highlighter shared/apl/code/ regex \"[:;?]\" 0:bright-black\nadd-highlighter shared/apl/code/ regex \"[←→]\" 0:normal\n# Number\nadd-highlighter shared/apl/code/ regex \"¯?[0-9][¯0-9A-Za-z]*(?:\\.[¯0-9Ee][¯0-9A-Za-z]*)*|¯?\\.[0-9Ee][¯0-9A-Za-z]*\" 0:value\nadd-highlighter shared/apl/code/ regex \"\\.\" 0:normal\nadd-highlighter shared/apl/code/ regex \"[⍺⍶⍵⍹χ∇]\" 0:blue # arguments\n# function\nadd-highlighter shared/apl/code/ regex \"[+\\-×÷⌈⌊∣|⍳?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⌸⍯↗⊆⊇⍸√⌾…⍮]\" 0:green  # function\n# operator\nadd-highlighter shared/apl/code/ regex \"[\\.\\\\/⌿⍀¨⍣⍨⍠⍤∘⌸&⌶@⌺⍥⍛⍢]\" 0:magenta\n# system\nadd-highlighter shared/apl/code/ regex \"⎕[A-Z_a-zÀ-ÖØ-Ýßà-öø-üþ∆⍙Ⓐ-Ⓩ][A-Z_a-zÀ-ÖØ-Ýßà-öø-üþ∆⍙Ⓐ-Ⓩ¯0-9]*\" 0:yellow \nadd-highlighter shared/apl/code/ regex \"\\n^\\s*\\n(\\n\\t[A-Z_a-zÀ-ÖØ-Ýßà-öø-üþ∆⍙Ⓐ-Ⓩ]\\n\\t[A-Z_a-zÀ-ÖØ-Ýßà-öø-üþ∆⍙Ⓐ-Ⓩ¯0-9]*\\n)\\n(:)\" 0:meta\n\ndeclare-user-mode apl\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden apl-insert-on-new-line %{\n    evaluate-commands -draft -itersel %[\n        # copy # comments prefix\n        try %{ execute-keys -draft <semicolon><c-s>k<a-x> s ^\\h*\\K#+\\h* <ret> y<c-o>P<esc> }\n    ]\n}\n\ndefine-command -hidden apl-indent-on-new-line %`\n    evaluate-commands -draft -itersel %_\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # indent after lines ending with { [\n        try %( execute-keys -draft k<a-x> <a-k> [{\\[]\\h*$ <ret> j<a-gt> )\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft k<a-x> s \\h+$ <ret>d }\n     _\n`\n\ndefine-command -hidden apl-indent-on-closing %`\n    evaluate-commands -draft -itersel %_\n        # align to opening bracket\n        try %( execute-keys -draft <a-h> <a-k> ^\\h*[}\\]]$ <ret> h m <a-S> 1<a-&> )\n    _\n`\n\n¹\n"
  },
  {
    "path": "rc/filetype/arch-linux.kak",
    "content": "# package build description file\nhook global BufCreate (.*/)?PKGBUILD %{\n    set-option buffer filetype sh\n}\n"
  },
  {
    "path": "rc/filetype/asciidoc.kak",
    "content": "# http://asciidoc.org/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .+\\.(a(scii)?doc|asc) %{\n    set-option buffer filetype asciidoc\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook -group asciidoc-highlight global WinSetOption filetype=asciidoc %{\n    require-module asciidoc\n\n    add-highlighter window/asciidoc ref asciidoc\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/asciidoc }\n}\n\nprovide-module asciidoc %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/asciidoc group\n\n# Titles and headers (multi-line style)\nadd-highlighter shared/asciidoc/ regex (\\A|\\n\\n)[^\\n]+\\n={2,}\\h*$ 0:title\nadd-highlighter shared/asciidoc/ regex (\\A|\\n\\n)[^\\n]+\\n-{2,}\\h*$ 0:header\nadd-highlighter shared/asciidoc/ regex (\\A|\\n\\n)[^\\n]+\\n~{2,}\\h*$ 0:header\nadd-highlighter shared/asciidoc/ regex (\\A|\\n\\n)[^\\n]+\\n\\^{2,}\\h*$ 0:header\n\n# Titles and headerss (one-line style)\nadd-highlighter shared/asciidoc/ regex (\\A|\\n\\n)=\\h+[^\\n]+$ 0:title\nadd-highlighter shared/asciidoc/ regex (\\A|\\n\\n)={2,}\\h+[^\\n]+$ 0:header\n\n# Comments\nadd-highlighter shared/asciidoc/ regex ^//(?:[^\\n/][^\\n]*|)$ 0:comment\nadd-highlighter shared/asciidoc/ regex ^(/{4,}).*?\\n(/{4,})$ 0:comment\n\n# List titles\nadd-highlighter shared/asciidoc/ regex ^\\.[^\\h\\W][^\\n]*$ 0:title\n\n# Bulleted lists\nadd-highlighter shared/asciidoc/ regex ^\\h*(?<bullet>[-\\*])\\h+[^\\n]+$ 0:list bullet:bullet\nadd-highlighter shared/asciidoc/ regex ^\\h*(?<bullet>[-\\*]+)\\h+[^\\n]+(\\n\\h+[^-\\*\\n]*)?$ 0:list bullet:bullet\n\n# Delimited blocks\n# https://docs.asciidoctor.org/asciidoc/latest/blocks/delimited/\nadd-highlighter shared/asciidoc/ regex ^(-{4,})\\n[^\\n\\h].*?\\n(-{4,})$ 0:block\nadd-highlighter shared/asciidoc/ regex ^(={4,})\\n[^\\n\\h].*?\\n(={4,})$ 0:block\nadd-highlighter shared/asciidoc/ regex ^(~{4,})\\n[^\\n\\h].*?\\n(~{4,})$ 0:block\nadd-highlighter shared/asciidoc/ regex ^(\\*{4,})\\n[^\\n\\h].*?\\n(\\*{4,})$ 0:block\n\n# Monospaced text\nadd-highlighter shared/asciidoc/ regex \\B(?:\\+[^\\n]+?\\+|`[^\\n]+?`)\\B 0:mono\n\n# Bolded text\nadd-highlighter shared/asciidoc/ regex \\s\\*[^\\n\\*]+\\*\\B 0:+b\nadd-highlighter shared/asciidoc/ regex \\h\\*[^\\n\\*]+\\*\\B 0:+b\nadd-highlighter shared/asciidoc/ regex \\*{2}(?!\\h)[^\\n\\*]+\\*{2} 0:+b\nadd-highlighter shared/asciidoc/ regex \\h\\*{2}[^\\n\\*]+\\*{2} 0:+b\n\n# Italicized text\n# (these are simpler since they aren't able to _also_ be bullet characters.)\nadd-highlighter shared/asciidoc/ regex \\b_[^\\n]+?_\\b 0:+i\nadd-highlighter shared/asciidoc/ regex __[^\\n]+?__ 0:+i\n\n# Attributes\nadd-highlighter shared/asciidoc/ regex ^:(?:(?<neg>!?)[-\\w]+|[-\\w]+(?<neg>!?)): 0:meta neg:operator\nadd-highlighter shared/asciidoc/ regex [^\\\\](\\{[-\\w]+\\})[^\\\\]? 1:meta\n\n# Options\nadd-highlighter shared/asciidoc/ regex ^\\[[^\\n]+\\]$ 0:operator\n\n# Admonition pargraphs\nadd-highlighter shared/asciidoc/ regex ^(NOTE|TIP|IMPORTANT|CAUTION|WARNING): 0:block\nadd-highlighter shared/asciidoc/ regex ^\\[(NOTE|TIP|IMPORTANT|CAUTION|WARNING)\\]$ 0:block\n\n# Links, inline macros\nadd-highlighter shared/asciidoc/ regex \\b((?:https?|ftp|irc://)[^\\h\\[]+)\\[([^\\n]*)?\\] 1:link 2:+i\nadd-highlighter shared/asciidoc/ regex (link|mailto):([^\\n]+)(?:\\[([^\\n]*)\\]) 1:keyword 2:link 3:+i\nadd-highlighter shared/asciidoc/ regex (xref):([^\\n]+)(?:\\[([^\\n]*)\\]) 1:keyword 2:meta 3:+i\nadd-highlighter shared/asciidoc/ regex (<<([^\\n><]+)>>) 1:link 2:meta\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\n}\n"
  },
  {
    "path": "rc/filetype/awk.kak",
    "content": "# Detection\n# ---------\n\nhook global BufCreate .*\\.awk %{\n    set-option buffer filetype awk\n}\n\n# Initialization\n# --------------\n\nhook global WinSetOption filetype=awk %{\n    require-module awk\n    \n    hook window InsertChar \\n -group awk-indent awk-indent-on-new-line\n    hook window ModeChange pop:insert:.* -group awk-trim-indent awk-trim-indent\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window awk-.+ }\n}\n\nhook -group awk-highlight global WinSetOption filetype=awk %{\n    add-highlighter window/awk ref awk\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/awk }\n}\n\nprovide-module awk %@\n\n# Highlighters\n# ------------\n\nadd-highlighter shared/awk regions\nadd-highlighter shared/awk/code default-region group\nadd-highlighter shared/awk/comment region '#' '$' fill comment\nadd-highlighter shared/awk/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/awk/regex region (\\A|[^\\s\\w)\\]+-]|\\bcase)\\s*\\K/ (?<!\\\\)(\\\\\\\\)*/ fill attribute\n\nadd-highlighter shared/awk/code/ regex (\\.\\d+|\\b\\d+\\.?\\d*)([eE][+-]?\\d+)?\\b 0:value # Decimal/octal/scientific\nadd-highlighter shared/awk/code/ regex \\b0[xX][\\da-fA-F]+\\b 0:value # Hexadecimal\nadd-highlighter shared/awk/code/ regex \\$|\\+|-|\\*|/|%|\\^|=|&&|\\||!|<|>|\\?|~ 0:operator\n\nevaluate-commands %sh{\n    # Grammar\n    patterns=\"BEGIN END BEGINFILE ENDFILE\"\n    variables=\"BINMODE CONVFMT FIELDWIDTHS FPAT FS IGNORECASE LINT OFMT OFS\n        ORS PREC ROUNDMODE RS SUBSEP TEXTDOMAIN ARGC ARGV ARGIND ENVIRON\n        ERRNO FILENAME FNR NF FUNCTAB NR PROCINFO RLENGTH RSTART RT SYMTAB\"\n    keywords=\"break continue delete exit function getline next print printf\n        return switch nextfile func if else while for do\"\n    functions=\"atan2 cos exp int intdiv log rand sin sqrt srand asort asort1\n        gensub gsub index length match patsplit split sprintf strtonum sub\n        substr tolower toupper close fflush system mktime strftime systime\n        and compl lshift or rshift xor isarray typeof bindtextdomain dcgettext\n        dcngetext\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"hook global WinSetOption filetype=awk %{\n        set-option window static_words $(join \"${patterns} ${variables} ${keywords} ${functions}\" ' ')\n    }\"\n\n    # Highlight keywords\n    printf %s\\\\n \"add-highlighter shared/awk/code/ regex \\b($(join \"${patterns}\" '|'))\\b 0:type\"\n    printf %s\\\\n \"add-highlighter shared/awk/code/ regex \\b($(join \"${variables}\" '|'))\\b 0:meta\"\n    printf %s\\\\n \"add-highlighter shared/awk/code/ regex \\b($(join \"${keywords}\" '|'))\\b 0:keyword\"\n    printf %s\\\\n \"add-highlighter shared/awk/code/ regex \\b($(join \"${functions}\" '|'))\\b 0:function\"\n}\n\n# Commands\n# --------\n\ndefine-command -hidden awk-indent-on-new-line %[\n    evaluate-commands -draft -itersel %[\n        # preserve previous line indent\n        try %[ execute-keys -draft <semicolon> K <a-&> ]\n        # cleanup trailing whitespaces from previous line\n        try %[ execute-keys -draft k x s \\h+$ <ret> d ]\n        # indent after line ending in opening curly brace\n        try %[ execute-keys -draft kx <a-k>\\{\\h*(#.*)?$<ret> j<a-gt> ]\n        # deindent closing brace when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> ]\n    ]\n]\n\ndefine-command -hidden awk-trim-indent %{\n    try %{ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d }\n}\n\n@\n"
  },
  {
    "path": "rc/filetype/c-family.kak",
    "content": "# Detection\nhook global BufCreate .*\\.(cc|cpp|cxx|C|hh|hpp|hxx|H)$ %{\n    set-option buffer filetype cpp\n}\n\nhook global BufSetOption filetype=c\\+\\+ %{\n    hook -once buffer NormalIdle '' \"set-option buffer filetype cpp\"\n}\n\nhook global BufCreate .*\\.c$ %{\n    set-option buffer filetype c\n}\n\nhook global BufCreate .*\\.h$ %{\n    try %{\n        execute-keys -draft %{%s\\b::\\b|\\btemplate\\h*<lt>|\\bclass\\h+\\w+|\\b(typename|namespace)\\b|\\b(public|private|protected)\\h*:<ret>}\n        set-option buffer filetype cpp\n    } catch %{\n        set-option buffer filetype c\n    }\n}\n\nhook global BufCreate .*\\.m %{\n    set-option buffer filetype objc\n}\n\nhook global WinSetOption filetype=(c|cpp|objc) %[\n    require-module c-family\n\n    evaluate-commands \"set-option window static_words %%opt{%val{hook_param_capture_1}_static_words}\"\n\n    hook -group \"%val{hook_param_capture_1}-trim-indent\" window ModeChange pop:insert:.* c-family-trim-indent\n    hook -group \"%val{hook_param_capture_1}-insert\" window InsertChar \\n c-family-insert-on-newline\n    hook -group \"%val{hook_param_capture_1}-indent\" window InsertChar \\n c-family-indent-on-newline\n    hook -group \"%val{hook_param_capture_1}-indent\" window InsertChar \\{ c-family-indent-on-opening-curly-brace\n    hook -group \"%val{hook_param_capture_1}-indent\" window InsertChar \\} c-family-indent-on-closing-curly-brace\n    hook -group \"%val{hook_param_capture_1}-insert\" window InsertChar \\} c-family-insert-on-closing-curly-brace\n\n    alias window alt \"%val{hook_param_capture_1}-alternative-file\"\n\n    hook -once -always window WinSetOption filetype=.* \"\n        remove-hooks window %val{hook_param_capture_1}-.+\n        unalias window alt %val{hook_param_capture_1}-alternative-file\n    \"\n]\n\nhook -group c-highlight global WinSetOption filetype=c %{\n    add-highlighter window/c ref c\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/c }\n}\n\nhook -group cpp-highlight global WinSetOption filetype=cpp %{\n    add-highlighter window/cpp ref cpp\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/cpp }\n}\n\nhook -group objc-highlight global WinSetOption filetype=objc %{\n    add-highlighter window/objc ref objc\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/objc }\n}\n\n\nprovide-module c-family %§\n\ndefine-command -hidden c-family-trim-indent %{\n    # remove the line if it's empty when leaving the insert mode\n    try %{ execute-keys -draft x 1s^(\\h+)$<ret> d }\n}\n\ndefine-command -hidden c-family-indent-on-newline %< evaluate-commands -draft -itersel %<\n    execute-keys <semicolon>\n    try %<\n        # if previous line is part of a comment, do nothing\n        execute-keys -draft <a-?>/\\*<ret> <a-K>^\\h*[^/*\\h]<ret>\n    > catch %<\n        # else if previous line closed a paren (possibly followed by words and a comment),\n        # copy indent of the opening paren line\n        execute-keys -draft kx 1s(\\))(\\h+\\w+)*\\h*(\\;\\h*)?(?://[^\\n]+)?\\n\\z<ret> m<a-semicolon>J <a-S> 1<a-&>\n    > catch %<\n        # else indent new lines with the same level as the previous one\n        execute-keys -draft K <a-&>\n    >\n    # remove previous empty lines resulting from the automatic indent\n    try %< execute-keys -draft k x <a-k>^\\h+$<ret> Hd >\n    # indent after an opening brace or parenthesis at end of line\n    try %< execute-keys -draft k x <a-k>[{(]\\h*$<ret> j <a-gt> >\n    # indent after a label\n    try %< execute-keys -draft k x s[a-zA-Z0-9_-]+:\\h*$<ret> j <a-gt> >\n    # indent after a statement not followed by an opening brace\n    try %< execute-keys -draft k x s\\)\\h*(?://[^\\n]+)?\\n\\z<ret> \\\n                               <a-semicolon>mB <a-k>\\A\\b(if|for|while)\\b<ret> <a-semicolon>j <a-gt> >\n    try %< execute-keys -draft k x s \\belse\\b\\h*(?://[^\\n]+)?\\n\\z<ret> \\\n                               j <a-gt> >\n    # deindent after a single line statement end\n    try %< execute-keys -draft K x <a-k>\\;\\h*(//[^\\n]+)?$<ret> \\\n                               K x s\\)(\\h+\\w+)*\\h*(//[^\\n]+)?\\n([^\\n]*\\n){2}\\z<ret> \\\n                               MB <a-k>\\A\\b(if|for|while)\\b<ret> <a-S>1<a-&> >\n    try %< execute-keys -draft K x <a-k>\\;\\h*(//[^\\n]+)?$<ret> \\\n                               K x s \\belse\\b\\h*(?://[^\\n]+)?\\n([^\\n]*\\n){2}\\z<ret> \\\n                               <a-S>1<a-&> >\n    # deindent closing brace(s) when after cursor\n    try %< execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <esc> m <a-S> 1<a-&> >\n    # align to the opening parenthesis or opening brace (whichever is first)\n    # on a previous line if its followed by text on the same line\n    try %< evaluate-commands -draft %<\n        # Go to opening parenthesis and opening brace, then select the most nested one\n        try %< execute-keys [c [({],[)}] <ret> >\n        # Validate selection and get first and last char\n        execute-keys <a-k>\\A[{(](\\h*\\S+)+\\n<ret> <a-K>\"(([^\"]*\"){2})*<ret> <a-K>'(([^']*'){2})*<ret> <a-:><a-semicolon>L <a-S>\n        # Remove possibly incorrect indent from new line which was copied from previous line\n        try %< execute-keys -draft , <a-h> s\\h+<ret> d >\n        # Now indent and align that new line with the opening parenthesis/brace\n        execute-keys 1<a-&> &\n     > >\n> >\n\ndefine-command -hidden c-family-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> <a-S> 1<a-&> ]\n    # align indent with opening paren when { is entered on a new line after the else\n    try %[ execute-keys -draft -itersel hK x s \\belse\\b\\h*(?://[^\\n]+)?\\n\\h*\\{<ret> <a-S> 1<a-&> ]\n]\n\ndefine-command -hidden c-family-indent-on-closing-curly-brace %[\n    evaluate-commands -draft -itersel -verbatim try %[\n        # check if alone on the line and select to opening curly brace\n        execute-keys <a-h><a-:><a-k>^\\h+\\}$<ret>hm\n        try %[\n            # in case open curly brace follows a closing paren possibly with qualifiers, extend to opening paren\n            execute-keys -draft <a-f>) <a-k> \\A\\)(\\h+\\w+)*\\h*\\{\\z <ret>\n            execute-keys <a-F>)M\n        ]\n        # align to selection start\n        execute-keys <a-S>1<a-&>\n    ]\n]\n\ndefine-command -hidden c-family-insert-on-closing-curly-brace %[\n    # add a semicolon after a closing brace if part of a class, union or struct definition\n    evaluate-commands -itersel -draft -verbatim try %[\n        execute-keys -draft hmh <a-?>\\b(class|struct|union|enum)\\b<ret> <a-K>\\{<ret> <a-K>\\)(\\s+\\w+)*\\s*\\z<ret>\n        execute-keys -draft ';i;<esc>'\n    ]\n]\n\ndefine-command -hidden c-family-insert-on-newline %[ evaluate-commands -itersel -draft %[\n    execute-keys <semicolon>\n    try %[\n        evaluate-commands -draft -save-regs '/\"' %[\n            # copy the commenting prefix\n            execute-keys -save-regs '' k x1s^\\h*(//+\\h*)<ret> y\n            try %[\n                # if the previous comment isn't empty, create a new one\n                execute-keys x<a-K>^\\h*//+\\h*$<ret> jxs^\\h*<ret>P\n            ] catch %[\n                # if there is no text in the previous comment, remove it completely\n                execute-keys d\n            ]\n        ]\n\n        # trim trailing whitespace on the previous line\n        try %[ execute-keys -draft k x s\\h+$<ret> d ]\n    ]\n    try %[\n        # if the previous line isn't within a comment scope, break\n        execute-keys -draft kx <a-k>^(\\h*/\\*|\\h+\\*(?!/))<ret>\n\n        # find comment opening, validate it was not closed, and check its using star prefixes\n        execute-keys -draft <a-?>/\\*<ret><a-H> <a-K>\\*/<ret> <a-k>\\A\\h*/\\*([^\\n]*\\n\\h*\\*)*[^\\n]*\\n\\h*.\\z<ret>\n\n        try %[\n            # if the previous line is opening the comment, insert star preceeded by space\n            execute-keys -draft kx<a-k>^\\h*/\\*<ret>\n            execute-keys -draft i*<space><esc>\n        ] catch %[\n           try %[\n                # if the next line is a comment line insert a star\n                execute-keys -draft jx<a-k>^\\h+\\*<ret>\n                execute-keys -draft i*<space><esc>\n            ] catch %[\n                try %[\n                    # if the previous line is an empty comment line, close the comment scope\n                    execute-keys -draft kx<a-k>^\\h+\\*\\h+$<ret> x1s\\*(\\h*)<ret>c/<esc>\n                ] catch %[\n                    # if the previous line is a non-empty comment line, add a star\n                    execute-keys -draft i*<space><esc>\n                ]\n            ]\n        ]\n\n        # trim trailing whitespace on the previous line\n        try %[ execute-keys -draft k x s\\h+$<ret> d ]\n        # align the new star with the previous one\n        execute-keys Kx1s^[^*]*(\\*)<ret>&\n    ]\n] ]\n\n# Regions definition are the same between c++ and objective-c\nevaluate-commands %sh{\n    for ft in c cpp objc; do\n        if [ \"${ft}\" = \"objc\" ]; then\n            maybe_at='@?'\n        else\n            maybe_at=''\n        fi\n\n        cat <<-EOF\n            add-highlighter shared/$ft regions\n            add-highlighter shared/$ft/code default-region group\n            add-highlighter shared/$ft/string region %{$maybe_at(?<!')(?<!'\\\\\\\\)\"} %{(?<!\\\\\\\\)(?:\\\\\\\\\\\\\\\\)*\"} fill string\n            add-highlighter shared/$ft/documentation_comment region /\\*(\\*[^/]|!) \\*/ fill documentation\n            add-highlighter shared/$ft/line_documentation_comment region //[/!] $ fill documentation\n            add-highlighter shared/$ft/comment region /\\\\* \\\\*/ fill comment\n            add-highlighter shared/$ft/line_comment region // (?<!\\\\\\\\)(?=\\\\n) fill comment\n            add-highlighter shared/$ft/disabled region -recurse \"#\\\\h*if(?:def)?\" ^\\\\h*?#\\\\h*if\\\\h+(?:0|FALSE)\\\\b \"#\\\\h*(?:else|elif|endif)\" fill comment\n            add-highlighter shared/$ft/ifdef region %{^\\\\h*\\\\K#\\\\h*(?:define|elif|if)(?=\\\\h)} %{(?<!\\\\\\\\)(?=\\\\s)|(?=//)} fill meta\n            add-highlighter shared/$ft/macro region %{^\\\\h*#} %{(?<!\\\\\\\\)(?=\\\\n)|(?=//)} group\n            add-highlighter shared/$ft/macro/ regex ^\\\\h*(#\\\\h*\\\\S*) 1:meta\n            add-highlighter shared/$ft/macro/ regex ^\\\\h*#\\\\h*include\\\\h+(<[^>]*>|\"(?:[^\"\\\\\\\\]|\\\\\\\\.)*\") 1:module\n            add-highlighter shared/$ft/macro/ regex /\\\\*.*?\\\\*/ 0:comment\n\tEOF\n    done\n}\n\n# c specific\nadd-highlighter shared/c/code/numbers regex %{\\b-?(0[xX][0-9a-fA-F]+|\\d+)([uU][lL]{0,2}|[lL]{1,2}[uU]?|[fFdDiI]|([eE][-+]?\\d+))?|'((\\\\.)?|[^'\\\\])'} 0:value\nevaluate-commands %sh{\n    # Grammar\n    keywords='asm break case continue default do else for goto if return\n              sizeof switch while offsetof alignas alignof'\n    attributes='auto atomic const enum extern inline register restrict static\n                struct typedef union volatile thread_local'\n    types='char double float int long short signed unsigned void\n           complex imaginary\n           fenv_t fexcept_t\n           imaxdiv_t\n           lconv\n           float_t double_t\n           jmp_buf\n           sig_atomic_t\n           va_list\n           memory_order atomic_flag atomic_bool atomic_char atomic_schar atomic_uchar atomic_wchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_llong atomic_ulong atomic_ullong atomic_char16_t atomic_char32_t atomic_intptr_t atomic_intmax_t atomic_int8_t atomic_int16_t atomic_int32_t atomic_int64_t atomic_int_least8_t atomic_int_least16_t atomic_int_least32_t atomic_int_least64_t atomic_int_fast8_t atomic_int_fast16_t atomic_int_fast32_t atomic_int_fast64_t atomic_uintptr_t atomic_uintmax_t atomic_uint8_t atomic_uint16_t atomic_uint32_t atomic_uint64_t atomic_uint_least8_t atomic_uint_least16_t atomic_uint_least32_t atomic_uint_least64_t atomic_uint_fast8_t atomic_uint_fast16_t atomic_uint_fast32_t atomic_uint_fast64_t atomic_size_t atomic_ptrdiff_t\n           bool\n           ptrdiff_t size_t max_align_t wchar_t\n           intptr_t intmax_t int8_t int16_t int32_t int64_t int_least8_t int_least16_t int_least32_t int_least64_t int_fast8_t int_fast16_t int_fast32_t int_fast64_t uintptr_t uintmax_t uint8_t uint16_t uint32_t uint64_t uint_least8_t uint_least16_t uint_least32_t uint_least64_t uint_fast8_t uint_fast16_t uint_fast32_t uint_fast64_t\n           FILE fpos_t\n           div_t ldiv_t lldiv_t\n           cnd_t thrd_t thrd_start_t tss_t tss_dtor_t mtx_t once_flag\n           clock_t time_t timespec tm\n           mbstate_t wint_t\n           wctrans_t wctype_t\n           char16_t char32_t\n           ssize_t gid_t uid_t off_t off64_t useconds_t pid_t socklen_t'\n\n    macros='assert static_assert NDEBUG\n            I\n            EDOM EILSEQ ERANGE errno\n            FE_DIVBYZERO FE_INEXACT FE_INVALID FE_OVERFLOW FE_UNDERFLOW FE_ALL_EXCEPT FE_DOWNWARD FE_TONEAREST FE_TOWARDZERO FE_UPWARD FE_DFL_ENV\n            DECIMAL_DIG FLT_ROUNDS FLT_EVAL_METHOD FLT_RADIX FLT_DIG FLT_MANT_DIG FLT_DECIMAL_DIG FLT_MIN_EXP FLT_MIN_10_EXP FLT_MAX_EXP FLT_MAX FLT_EPSILON FLT_TRUE_MIN FLT_HAS_SUBNORM DBL_DIG DBL_MANT_DIG DBL_DECIMAL_DIG DBL_MIN_EXP DBL_MIN_10_EXP DBL_MAX_EXP DBL_MAX DBL_EPSILON DBL_TRUE_MIN DBL_HAS_SUBNORM LDBL_DIG LDBL_MANT_DIG LDBL_DECIMAL_DIG LDBL_MIN_EXP LDBL_MIN_10_EXP LDBL_MAX_EXP LDBL_MAX LDBL_EPSILON LDBL_TRUE_MIN LDBL_HAS_SUBNORM\n            PRIdMAX PRIdPTR PRId8 PRId16 PRId32 PRId64 PRIdLEAST8 PRIdLEAST16 PRIdLEAST32 PRIdLEAST64 PRIdFAST8 PRIdFAST16 PRIdFAST32 PRIdFAST64 PRIiMAX PRIiPTR PRIi8 PRIi16 PRIi32 PRIi64 PRIiLEAST8 PRIiLEAST16 PRIiLEAST32 PRIiLEAST64 PRIiFAST8 PRIiFAST16 PRIiFAST32 PRIiFAST64 PRIoMAX PRIoPTR PRIo8 PRIo16 PRIo32 PRIo64 PRIoLEAST8 PRIoLEAST16 PRIoLEAST32 PRIoLEAST64 PRIoFAST8 PRIoFAST16 PRIoFAST32 PRIoFAST64 PRIuMAX PRIuPTR PRIu8 PRIu16 PRIu32 PRIu64 PRIuLEAST8 PRIuLEAST16 PRIuLEAST32 PRIuLEAST64 PRIuFAST8 PRIuFAST16 PRIuFAST32 PRIuFAST64 PRIxMAX PRIxPTR PRIx8 PRIx16 PRIx32 PRIx64 PRIxLEAST8 PRIxLEAST16 PRIxLEAST32 PRIxLEAST64 PRIxFAST8 PRIxFAST16 PRIxFAST32 PRIxFAST64 PRIXMAX PRIXPTR PRIX8 PRIX16 PRIX32 PRIX64 PRIXLEAST8 PRIXLEAST16 PRIXLEAST32 PRIXLEAST64 PRIXFAST8 PRIXFAST16 PRIXFAST32 PRIXFAST64 SCNdMAX SCNdPTR SCNd8 SCNd16 SCNd32 SCNd64 SCNdLEAST8 SCNdLEAST16 SCNdLEAST32 SCNdLEAST64 SCNdFAST8 SCNdFAST16 SCNdFAST32 SCNdFAST64 SCNiMAX SCNiPTR SCNi8 SCNi16 SCNi32 SCNi64 SCNiLEAST8 SCNiLEAST16 SCNiLEAST32 SCNiLEAST64 SCNiFAST8 SCNiFAST16 SCNiFAST32 SCNiFAST64 SCNoMAX SCNoPTR SCNo8 SCNo16 SCNo32 SCNo64 SCNoLEAST8 SCNoLEAST16 SCNoLEAST32 SCNoLEAST64 SCNoFAST8 SCNoFAST16 SCNoFAST32 SCNoFAST64 SCNuMAX SCNuPTR SCNu8 SCNu16 SCNu32 SCNu64 SCNuLEAST8 SCNuLEAST16 SCNuLEAST32 SCNuLEAST64 SCNuFAST8 SCNuFAST16 SCNuFAST32 SCNuFAST64 SCNxMAX SCNxPTR SCNx8 SCNx16 SCNx32 SCNx64 SCNxLEAST8 SCNxLEAST16 SCNxLEAST32 SCNxLEAST64 SCNxFAST8 SCNxFAST16 SCNxFAST32 SCNxFAST64\n            and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq\n            CHAR_MIN CHAR_MAX SCHAR_MIN SCHAR_MAX WCHAR_MIN WCHAR_MAX SHRT_MIN SHRT_MAX INT_MIN INT_MAX LONG_MIN LONG_MAX LLONG_MIN LLONG_MAX MB_LEN_MAX UCHAR_MAX USHRT_MAX UINT_MAX ULONG_MAX ULLONG_MAX CHAR_BIT\n            LC_ALL LC_COLLATE LC_CTYPE LC_MONETARY LC_NUMERIC LC_TIME\n            HUGE_VAL HUGE_VALF HUGE_VALL INFINITY NAN FP_INFINITE FP_NAN FP_NORMAL FP_SUBNORMAL FP_ZERO FP_FAST_FMA FP_FAST_FMAF FP_FAST_FMAL FP_ILOGB0 FP_ILOGBNAN MATH_ERRNO MATH_ERREXCEPT math_errhandling isgreater isgreaterequal isless islessequal islessgreater isunordered\n            setjmp\n            SIG_DFL SIG_ERR SIG_IGN SIGABRT SIGFPE SIGILL SIGINT SIGSEGV SIGTERM\n            va_start va_arg va_end va_copy\n            ATOMIC_BOOL_LOCK_FREE ATOMIC_CHAR_LOCK_FREE ATOMIC_CHAR16_T_LOCK_FREE ATOMIC_CHAR32_T_LOCK_FREE ATOMIC_WCHAR_T_LOCK_FREE ATOMIC_SHORT_LOCK_FREE ATOMIC_INT_LOCK_FREE ATOMIC_LONG_LOCK_FREE ATOMIC_LLONG_LOCK_FREE ATOMIC_POINTER_LOCK_FREE ATOMIC_FLAG_INIT ATOMIC_VAR_INIT memory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst kill_dependency\n            true false\n            NULL\n            _IOFBF _IOLBF _IONBF BUFSIZ EOF FOPEN_MAX FILENAME_MAX TMP_MAX L_tmpnam SEEK_CUR SEEK_END SEEK_SET stderr stdin stdout\n            EXIT_FAILURE EXIT_SUCCESS MB_CUR_MAX RAND_MAX\n            PTRDIFF_MIN PTRDIFF_MAX SIG_ATOMIC_MIN SIG_ATOMIC_MAX WINT_MIN WINT_MAX INTMAX_MIN INTMAX_MAX INTPTR_MIN INTPTR_MAX INT8_MIN INT8_MAX INT16_MIN INT16_MAX INT32_MIN INT32_MAX INT64_MIN INT64_MAX INT_LEAST8_MIN INT_LEAST8_MAX INT_LEAST16_MIN INT_LEAST16_MAX INT_LEAST32_MIN INT_LEAST32_MAX INT_LEAST64_MIN INT_LEAST64_MAX INT_FAST8_MIN INT_FAST8_MAX INT_FAST16_MIN INT_FAST16_MAX INT_FAST32_MIN INT_FAST32_MAX INT_FAST64_MIN INT_FAST64_MAX UINTMAX_MAX UINTPTR_MAX UINT8_MAX UINT16_MAX UINT32_MAX UINT64_MAX UINT_LEAST8_MAX UINT_LEAST16_MAX UINT_LEAST32_MAX UINT_LEAST64_MAX UINT_FAST8_MAX UINT_FAST16_MAX UINT_FAST32_MAX UINT_FAST64_MAX INTMAX_C INT8_C INT16_C INT32_C INT64_C UINTMAX_C UINT8_C UINT16_C UINT32_C UINT64_C\n            mtx_plain mtx_recursive mtx_timed thrd_timedout thrd_success thrd_busy thrd_error thrd_nomem ONCE_FLAG_INIT TSS_DTOR_ITERATION\n            CLOCKS_PER_SEC TIME_UTC\n            WEOF\n            noreturn\n            R_OK W_OK X_OK F_OK F_LOCK F_ULOCK F_TLOCK F_TEST'\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list c_static_words $(join \"${keywords} ${attributes} ${types} ${macros}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/c/code/keywords regex \\b($(join \"${keywords}\" '|'))\\b 0:keyword\n        add-highlighter shared/c/code/attributes regex \\b($(join \"${attributes}\" '|'))\\b 0:attribute\n        add-highlighter shared/c/code/types regex \\b($(join \"${types}\" '|'))\\b 0:type\n        add-highlighter shared/c/code/values regex \\b($(join \"${macros}\" '|'))\\b 0:value\n    \"\n}\n\n# c++ specific\n\n# raw strings\nadd-highlighter shared/cpp/raw_string region -match-capture %{R\"([^(]*)\\(} %{\\)([^\")]*)\"} fill string\n\n# integer literals\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)\\b[1-9]('?\\d+)*(ul?l?|ll?u?)?\\b(?!\\.)} 0:value\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)\\b0b[01]('?[01]+)*(ul?l?|ll?u?)?\\b(?!\\.)} 0:value\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)\\b0('?[0-7]+)*(ul?l?|ll?u?)?\\b(?!\\.)} 0:value\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)\\b0x[\\da-f]('?[\\da-f]+)*(ul?l?|ll?u?)?\\b(?!\\.)} 0:value\n\n# floating point literals\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)\\b\\d('?\\d+)*\\.([fl]\\b|\\B)(?!\\.)} 0:value\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)\\b\\d('?\\d+)*\\.?e[+-]?\\d('?\\d+)*[fl]?\\b(?!\\.)} 0:value\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)(\\b(\\d('?\\d+)*)|\\B)\\.\\d('?[\\d]+)*(e[+-]?\\d('?\\d+)*)?[fl]?\\b(?!\\.)} 0:value\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)\\b0x[\\da-f]('?[\\da-f]+)*\\.([fl]\\b|\\B)(?!\\.)} 0:value\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)\\b0x[\\da-f]('?[\\da-f]+)*\\.?p[+-]?\\d('?\\d+)*)?[fl]?\\b(?!\\.)} 0:value\nadd-highlighter shared/cpp/code/ regex %{(?i)(?<!\\.)\\b0x([\\da-f]('?[\\da-f]+)*)?\\.\\d('?[\\d]+)*(p[+-]?\\d('?\\d+)*)?[fl]?\\b(?!\\.)} 0:value\n\n# character literals (no multi-character literals)\nadd-highlighter shared/cpp/code/char regex %{(\\b(u8|u|U|L)|\\B)'((\\\\.)|[^'\\\\])'\\B} 0:value\n\nevaluate-commands %sh{\n    # Grammar\n    keywords='alignas alignof and and_eq asm bitand bitor break case catch\n              compl const_cast continue decltype delete do dynamic_cast\n              else export for goto if new not not_eq operator or or_eq\n              reinterpret_cast return sizeof static_assert static_cast switch\n              throw try typedef typeid using while xor xor_eq'\n    attributes='audit auto axiom const consteval constexpr default explicit\n                extern final friend inline mutable noexcept override private\n                protected public register requires static thread_local typename\n                virtual volatile'\n    entities='class concept enum namespace struct template union'\n    types='bool byte char char8_t char16_t char32_t double float int long\n           max_align_t nullptr_t ptrdiff_t short signed size_t unsigned void\n           wchar_t'\n    values='NULL false nullptr this true'\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list cpp_static_words $(join \"${keywords} ${attributes} ${entities} ${types} ${values}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/cpp/code/keywords regex \\b($(join \"${keywords}\" '|'))\\b 0:keyword\n        add-highlighter shared/cpp/code/attributes regex \\b($(join \"${attributes} ${entities}\" '|'))\\b 0:attribute\n        add-highlighter shared/cpp/code/types regex \\b($(join \"${types}\" '|'))\\b 0:type\n        add-highlighter shared/cpp/code/values regex \\b($(join \"${values}\" '|'))\\b 0:value\n    \"\n}\n\n# c and c++ compiler macros\nevaluate-commands %sh{\n    builtin_macros=\"__cplusplus|__STDC_HOSTED__|__FILE__|__LINE__|__DATE__|__TIME__|__STDCPP_DEFAULT_NEW_ALIGNMENT__\"\n\n    printf %s \"\n        add-highlighter shared/c/code/macros regex \\b(${builtin_macros})\\b 0:builtin\n        add-highlighter shared/cpp/code/macros regex \\b(${builtin_macros})\\b 0:builtin\n    \"\n}\n\n# objective-c specific\nadd-highlighter shared/objc/code/number regex %{\\b-?\\d+[fdiu]?|'((\\\\.)?|[^'\\\\])'} 0:value\n\nevaluate-commands %sh{\n    # Grammar\n    keywords='break case continue default do else for goto if return switch\n              while'\n    attributes='IBAction IBOutlet __block assign auto const copy enum extern\n                inline nonatomic readonly retain static strong struct typedef\n                union volatile weak'\n    types='BOOL CGFloat NSInteger NSString NSUInteger bool char float\n           instancetype int long short signed size_t unsigned void'\n    values='FALSE NO NULL TRUE YES id nil self super'\n    decorators='autoreleasepool catch class end implementation interface\n                property protocol selector synchronized synthesize try'\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list objc_static_words $(join \"${keywords} ${attributes} ${types} ${values} ${decorators}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/objc/code/keywords regex \\b($(join \"${keywords}\" '|'))\\b 0:keyword\n        add-highlighter shared/objc/code/attributes regex \\b($(join \"${attributes}\" '|'))\\b 0:attribute\n        add-highlighter shared/objc/code/types regex \\b($(join \"${types}\" '|'))\\b 0:type\n        add-highlighter shared/objc/code/values regex \\b($(join \"${values}\" '|'))\\b 0:value\n        add-highlighter shared/objc/code/decorators regex  @($(join \"${decorators}\" '|'))\\b 0:attribute\n    \"\n}\n\ndeclare-option -docstring %{\n    control the type of include guard to be inserted in empty headers\n    Can be one of the following:\n        ifdef: old style ifndef/define guard\n        pragma: newer type of guard using \"pragma once\"\n} str c_include_guard_style \"ifdef\"\n\ndefine-command -hidden c-family-insert-include-guards %{\n    evaluate-commands %sh{\n        case \"${kak_opt_c_include_guard_style}\" in\n            ifdef)\n                echo 'execute-keys gg\"%PI/<esc>xs^.*/<ret>dxs\\.<ret>r_A_INCLUDED<esc>xyPPI#ifndef<space><esc>jI#define<space><esc>jI#endif<space>//<space><esc>O<esc>'\n                ;;\n            pragma)\n                echo 'execute-keys ggi#pragma<space>once<esc>'\n                ;;\n            *);;\n        esac\n    }\n}\n\nhook -group c-family-insert global BufNewFile .*\\.(h|hh|hpp|hxx|H) c-family-insert-include-guards\n\ndeclare-option -docstring \"colon separated list of path in which header files will be looked for\" \\\n    str-list alt_dirs '.' '..'\n\ndefine-command -hidden c-family-alternative-file %{\n    evaluate-commands %sh{\n        file=\"${kak_buffile##*/}\"\n        file_noext=\"${file%.*}\"\n        dir=$(dirname \"${kak_buffile}\")\n\n        # Set $@ to alt_dirs\n        eval \"set -- ${kak_quoted_opt_alt_dirs}\"\n\n        case ${file} in\n            *.c|*.cc|*.cpp|*.cxx|*.C|*.inl|*.m)\n                for alt_dir in \"$@\"; do\n                    for ext in h hh hpp hxx H; do\n                        case \"$alt_dir\" in\n                        /*) altname=\"${alt_dir}/${file_noext}.${ext}\" ;;\n                        *) altname=\"${dir}/${alt_dir}/${file_noext}.${ext}\" ;;\n                        esac\n                        if [ -f \"${altname}\" ]; then\n                            printf 'edit %%{%s}\\n' \"${altname}\"\n                            exit\n                        fi\n                    done\n                done\n            ;;\n            *.h|*.hh|*.hpp|*.hxx|*.H)\n                for alt_dir in \"$@\"; do\n                    for ext in c cc cpp cxx C m; do\n                        case \"$alt_dir\" in\n                        /*) altname=\"${alt_dir}/${file_noext}.${ext}\" ;;\n                        *) altname=\"${dir}/${alt_dir}/${file_noext}.${ext}\" ;;\n                        esac\n                        if [ -f \"${altname}\" ]; then\n                            printf 'edit %%{%s}\\n' \"${altname}\"\n                            exit\n                        fi\n                    done\n                done\n            ;;\n            *)\n                echo \"fail 'extension not recognized'\"\n                exit\n            ;;\n        esac\n        echo \"fail 'alternative file not found'\"\n    }\n}\n\ndefine-command c-alternative-file -docstring \"Jump to the alternate c file (header/implementation)\" %{\n    c-family-alternative-file\n}\ndefine-command cpp-alternative-file -docstring \"Jump to the alternate cpp file (header/implementation)\" %{\n    c-family-alternative-file\n}\ndefine-command objc-alternative-file -docstring \"Jump to the alternate objc file (header/implementation)\" %{\n    c-family-alternative-file\n}\n\n§\n\n# Module aliases\nprovide-module c %{ require-module c-family }\nprovide-module cpp %{ require-module c-family }\nprovide-module objc %{ require-module c-family }\n"
  },
  {
    "path": "rc/filetype/cabal.kak",
    "content": "# http://haskell.org/cabal\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](cabal) %{\n    set-option buffer filetype cabal\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=cabal %[\n    require-module cabal\n\n    hook window ModeChange pop:insert:.* -group cabal-trim-indent cabal-trim-indent\n    hook window InsertChar \\n -group cabal-insert cabal-insert-on-new-line\n    hook window InsertChar \\n -group cabal-indent cabal-indent-on-new-line\n    hook window InsertChar \\{ -group cabal-indent cabal-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group cabal-indent cabal-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window cabal-.+ }\n]\n\nhook -group cabal-highlight global WinSetOption filetype=cabal %{\n    add-highlighter window/cabal ref cabal\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/cabal }\n}\n\n\nprovide-module cabal %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/cabal regions\nadd-highlighter shared/cabal/code default-region group\nadd-highlighter shared/cabal/line_comment region (--) $ fill comment\nadd-highlighter shared/cabal/comment region -recurse \\{- \\{- -\\} fill comment\n\nadd-highlighter shared/cabal/code/ regex \\b(true|false)\\b|(([<>]?=?)?\\d+(\\.\\d+)+) 0:value\nadd-highlighter shared/cabal/code/ regex \\b(if|else)\\b 0:keyword\nadd-highlighter shared/cabal/code/ regex ^\\h*([A-Za-z][A-Za-z0-9_-]*)\\h*: 1:variable\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden cabal-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden cabal-insert-on-new-line %[\n    evaluate-commands -draft -itersel %[\n        # copy '--' comment prefix and following white spaces\n        try %[ execute-keys -draft k x s ^\\h*\\K--\\h* <ret> y gh j P ]\n    ]\n]\n\ndefine-command -hidden cabal-indent-on-new-line %[\n    evaluate-commands -draft -itersel %[\n        # preserve previous line indent\n        try %[ execute-keys -draft <semicolon> K <a-&> ]\n        # filter previous line\n        try %[ execute-keys -draft k : cabal-trim-indent <ret> ]\n        # indent after lines ending with { or :\n        try %[ execute-keys -draft , k x <a-k> [:{]$ <ret> j <a-gt> ]\n        # deindent closing brace when after cursor\n        try %[ execute-keys -draft x <a-k> \\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> ]\n    ]\n]\n\ndefine-command -hidden cabal-indent-on-opening-curly-brace %[\n    evaluate-commands -draft -itersel %[\n        # align indent with opening paren when { is entered on a new line after the closing paren\n        try %[ execute-keys -draft h <a-F> ) M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n    ]\n]\n\ndefine-command -hidden cabal-indent-on-closing-curly-brace %[\n    evaluate-commands -draft -itersel %[\n        # align to opening curly brace when alone on a line\n        try %[ execute-keys -draft <a-h> <a-k> ^\\h+\\}$ <ret> h m s \\A|.\\z<ret> 1<a-&> ]\n    ]\n]\n\n]\n"
  },
  {
    "path": "rc/filetype/capnp.kak",
    "content": "# https://capnproto.org/\n\nhook global BufCreate .*[.](capnp) %{\n    set-option buffer filetype capnp\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=capnp %{\n    require-module capnp\n\n    set-option window static_words %opt{capnp_static_words}\n\n    hook window ModeChange pop:insert:.* -group capnp-trim-indent capnp-trim-indent\n    hook window InsertChar .* -group capnp-indent capnp-indent-on-char\n    hook window InsertChar \\n -group capnp-indent capnp-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window capnp-.+ }\n}\n\nhook -group capnp-highlight global WinSetOption filetype=capnp %{\n    add-highlighter window/capnp ref capnp\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/capnp }\n}\n\nprovide-module capnp %@\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n \nadd-highlighter shared/capnp regions\nadd-highlighter shared/capnp/code default-region group\n\nadd-highlighter shared/capnp/line_comment region '#' '$' fill comment\nadd-highlighter shared/capnp/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\n\nadd-highlighter shared/capnp/code/ regex '(?i)\\b0b[01]+l?\\b' 0:value\nadd-highlighter shared/capnp/code/ regex '(?i)\\b0x[\\da-f]+l?\\b' 0:value\nadd-highlighter shared/capnp/code/ regex '(?i)\\b0o?[0-7]+l?\\b' 0:value\nadd-highlighter shared/capnp/code/ regex '(?i)\\b([1-9]\\d*|0)l?\\b' 0:value\nadd-highlighter shared/capnp/code/ regex '\\b\\d+[eE][+-]?\\d+\\b' 0:value\nadd-highlighter shared/capnp/code/ regex '(\\b\\d+)?\\.\\d+\\b' 0:value\nadd-highlighter shared/capnp/code/ regex '\\b\\d+\\.' 0:value\n\nevaluate-commands %sh{\n    builtin_types=\"Void Bool Text Data List union group Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64 Float32 Float64\"\n    declarations=\"struct union enum interface const annotation\"\n    keywords=\"using extends import\"\n    values=\"true false inf\"\n    \n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    printf %s\\\\n \"declare-option str-list capnp_static_words $(join \"${builtin_types}\" ' ') $(join \"${declarations}\" ' ') $(join \"${keywords}\" ' ') $(join \"${values}\" ' ')\"\n\n    printf %s\\\\n \"add-highlighter shared/capnp/code/ regex '\\b($(join \"${builtin_types}\" '|'))\\b' 0:type\"\n    printf %s\\\\n \"add-highlighter shared/capnp/code/ regex '\\b($(join \"${declarations}\" '|'))\\b' 0:keyword\"\n    printf %s\\\\n \"add-highlighter shared/capnp/code/ regex '\\b($(join \"${keywords}\" '|'))\\b' 0:keyword\"\n    printf %s\\\\n \"add-highlighter shared/capnp/code/ regex '\\b($(join \"${values}\" '|'))\\b' 0:value\"\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden capnp-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden capnp-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m <a-S> 1<a-&> >\n    >\n>\n\ndefine-command -hidden capnp-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : capnp-trim-indent <ret> }\n        # indent after lines ending with opener token\n        try %< execute-keys -draft k x <a-k> [[{]\\h*$ <ret> j <a-gt> >\n        # deindent closer token(s) when after cursor\n        try %< execute-keys -draft x <a-k> ^\\h*[}\\]] <ret> gh / [}\\]] <ret> m <a-S> 1<a-&> >\n    >\n>\n\n@\n"
  },
  {
    "path": "rc/filetype/clojure.kak",
    "content": "# http://clojure.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](clj|cljc|cljs|cljx|edn) %{\n    set-option buffer filetype clojure\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\nhook global WinSetOption filetype=clojure %{\n    require-module clojure\n    clojure-configure-window\n}\n\nhook -group clojure-highlight global WinSetOption filetype=clojure %{\n    add-highlighter window/clojure ref clojure\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/clojure }\n}\n\nhook -group clojure-insert global BufNewFile .*[.](clj|cljc|cljs|cljx) %{\n    require-module clojure\n    clojure-insert-ns\n}\n\nprovide-module clojure %{\n\nrequire-module lisp\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/clojure regions\nadd-highlighter shared/clojure/code default-region group\nadd-highlighter shared/clojure/comment region '(?<!\\\\)(?:\\\\\\\\)*\\K;' '$'                 fill comment\nadd-highlighter shared/clojure/shebang region '(?<!\\\\)(?:\\\\\\\\)*\\K#!' '$'                fill comment\nadd-highlighter shared/clojure/string  region '(?<!\\\\)(?:\\\\\\\\)*\\K\"' '(?<!\\\\)(?:\\\\\\\\)*\"' fill string\n\nadd-highlighter shared/clojure/code/ regex \\b(nil|true|false)\\b 0:value\nadd-highlighter shared/clojure/code/ regex \\\n    \\\\(?:space|tab|newline|return|backspace|formfeed|u[0-9a-fA-F]{4}|o[0-3]?[0-7]{1,2}|.)\\b 0:string\n\nevaluate-commands %sh{\n    exec awk -f - <<'EOF'\n    BEGIN{\n        symbol_char=\"[^\\\\s()\\\\[\\\\]{}\\\"\\\\;@^`~\\\\\\\\%/]\";\n        in_core=\"(clojure\\\\.core/|(?<!/))\";\n        split( \\\n        \"case cond condp cond-> cond->> def definline definterface defmacro defmethod \"\\\n        \"defmulti defn defn- defonce defprotocol defrecord defstruct deftype fn if \"\\\n        \"if-let if-not if-some let letfn new ns when when-first when-let when-not \"\\\n        \"when-some . ..\", keywords);\n\n        split( \\\n        \"* *' + +' - -' -> ->> ->ArrayChunk ->Eduction ->Vec ->VecNode ->VecSeq / < \"\\\n        \"<= = == > >= StackTraceElement->vec Throwable->map accessor aclone \"\\\n        \"add-classpath add-watch agent agent-error agent-errors aget alength alias \"\\\n        \"all-ns alter alter-meta! alter-var-root amap ancestors and any? apply \"\\\n        \"areduce array-map as-> aset aset-boolean aset-byte aset-char aset-double \"\\\n        \"aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in \"\\\n        \"associative? atom await await-for bases bean bigdec bigint biginteger \"\\\n        \"binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set \"\\\n        \"bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array \"\\\n        \"boolean? booleans bound-fn bound-fn* bound? bounded-count butlast byte \"\\\n        \"byte-array bytes bytes?  cast cat catch char char-array char-escape-string \"\\\n        \"char-name-string char? chars class class? clear-agent-errors \"\\\n        \"clojure-version coll? comment commute comp comparator compare \"\\\n        \"compare-and-set! compile complement completing concat  conj conj! cons \"\\\n        \"constantly construct-proxy contains? count counted? create-ns \"\\\n        \"create-struct cycle dec dec' decimal? declare dedupe default-data-readers \"\\\n        \"delay delay? deliver denominator deref derive descendants disj disj! \"\\\n        \"dissoc dissoc! distinct distinct? do doall dorun doseq dosync dotimes doto \"\\\n        \"double double-array double? doubles drop drop-last drop-while eduction \"\\\n        \"empty empty? ensure ensure-reduced enumeration-seq error-handler \"\\\n        \"error-mode eval even? every-pred every? ex-data ex-info extend \"\\\n        \"extend-protocol extend-type extenders extends? false? ffirst file-seq \"\\\n        \"filter filterv finally find find-keyword find-ns find-var first flatten \"\\\n        \"float float-array float? floats flush fn? fnext fnil for force format \"\\\n        \"frequencies future future-call future-cancel future-cancelled? \"\\\n        \"future-done? future? gen-class gen-interface gensym get get-in get-method \"\\\n        \"get-proxy-class get-thread-bindings get-validator group-by halt-when hash \"\\\n        \"hash-map hash-ordered-coll hash-set hash-unordered-coll ident? identical? \"\\\n        \"identity ifn? import in-ns inc inc' indexed? init-proxy inst-ms inst? \"\\\n        \"instance? int int-array int? integer? interleave intern interpose into \"\\\n        \"into-array ints io! isa? iterate iterator-seq juxt keep keep-indexed key \"\\\n        \"keys keyword keyword? last lazy-cat lazy-seq line-seq list list* list? \"\\\n        \"load load-file load-reader load-string loaded-libs locking long long-array \"\\\n        \"longs loop macroexpand macroexpand-1 make-array make-hierarchy map \"\\\n        \"map-entry? map-indexed map? mapcat mapv max max-key memfn memoize merge \"\\\n        \"merge-with meta methods min min-key mix-collection-hash mod monitor-enter \"\\\n        \"monitor-exit name namespace namespace-munge nat-int? neg-int? neg? newline \"\\\n        \"next nfirst nil? nnext not not-any? not-empty not-every? not= ns-aliases \"\\\n        \"ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve \"\\\n        \"ns-unalias ns-unmap nth nthnext nthrest num number? numerator object-array \"\\\n        \"odd? or parents partial partition partition-all partition-by pcalls peek \"\\\n        \"persistent! pmap pop pop! pop-thread-bindings pos-int? pos? pr pr-str \"\\\n        \"prefer-method prefers print print-str printf println println-str prn \"\\\n        \"prn-str promise proxy proxy-mappings proxy-super push-thread-bindings \"\\\n        \"pvalues qualified-ident? qualified-keyword? qualified-symbol? quot quote \"\\\n        \"rand rand-int rand-nth random-sample range ratio? rational? rationalize \"\\\n        \"re-find re-groups re-matcher re-matches re-pattern re-seq read read-line \"\\\n        \"read-string reader-conditional reader-conditional? realized? record? recur \"\\\n        \"reduce reduce-kv reduced reduced? reductions ref ref-history-count \"\\\n        \"ref-max-history ref-min-history ref-set refer refer-clojure reify \"\\\n        \"release-pending-sends rem remove remove-all-methods remove-method \"\\\n        \"remove-ns remove-watch repeat repeatedly replace replicate require reset! \"\\\n        \"reset-meta! reset-vals! resolve rest restart-agent resultset-seq reverse \"\\\n        \"reversible? rseq rsubseq run! satisfies? second select-keys send send-off \"\\\n        \"send-via seq seq? seqable? seque sequence sequential? set set! \"\\\n        \"set-agent-send-executor! set-agent-send-off-executor! set-error-handler! \"\\\n        \"set-error-mode! set-validator! set? short short-array shorts shuffle \"\\\n        \"shutdown-agents simple-ident? simple-keyword? simple-symbol? slurp some \"\\\n        \"some-> some->> some-fn some? sort sort-by sorted-map sorted-map-by \"\\\n        \"sorted-set sorted-set-by sorted? special-symbol? spit split-at split-with \"\\\n        \"str string? struct struct-map subs subseq subvec supers swap! swap-vals! \"\\\n        \"symbol symbol? sync tagged-literal tagged-literal? take take-last take-nth \"\\\n        \"take-while test the-ns thread-bound? throw time to-array to-array-2d \"\\\n        \"trampoline transduce transient tree-seq true? try type unchecked-add \"\\\n        \"unchecked-add-int unchecked-byte unchecked-char unchecked-dec \"\\\n        \"unchecked-dec-int unchecked-divide-int unchecked-double unchecked-float \"\\\n        \"unchecked-inc unchecked-inc-int unchecked-int unchecked-long \"\\\n        \"unchecked-multiply unchecked-multiply-int unchecked-negate \"\\\n        \"unchecked-negate-int unchecked-remainder-int unchecked-short \"\\\n        \"unchecked-subtract unchecked-subtract-int underive unreduced \"\\\n        \"unsigned-bit-shift-right update update-in update-proxy uri? use uuid? val \"\\\n        \"vals var var-get var-set var? vary-meta vec vector vector-of vector? \"\\\n        \"volatile! volatile? vreset! vswap!  while with-bindings with-bindings* \"\\\n        \"with-in-str with-local-vars with-meta with-open with-out-str \"\\\n        \"with-precision with-redefs with-redefs-fn xml-seq zero? zipmap\", core_fns);\n\n        split( \\\n        \"*1 *2 *3 *agent* *clojure-version* *command-line-args* *compile-files* \"\\\n        \"*compile-path* *compiler-options* *data-readers* *default-data-reader-fn* \"\\\n        \"*e *err* *file* *flush-on-newline* *in* *ns* *out* *print-dup* \"\\\n        \"*print-length* *print-level* *print-meta* *print-namespace-maps* \"\\\n        \"*print-readably* *read-eval* *unchecked-math* *warn-on-reflection*\", core_vars);\n    }\n    function print_word_highlighter(words, face, first) {\n        printf(\"add-highlighter shared/clojure/code/ regex (?<!%s)%s(\", \\\n               symbol_char, in_core);\n        first = 1;\n        for (i in words) {\n            if (!first) { printf(\"|\"); }\n            printf(\"\\\\Q%s\\\\E\", words[i]);\n            first = 0;\n        }\n        printf(\")(?!%s) 0:%s\\n\", symbol_char, face);\n    }\n    function print_static_words(words) {\n        for (i in words) {\n            printf(\"%s clojure.core/%s \", words[i], words[i]);\n        }\n    }\n    BEGIN{\n        # Keywords\n        printf(\"add-highlighter shared/clojure/code/ regex ::?(%s+/)?%s+ 0:value\\n\", symbol_char, symbol_char);\n\n        # Numbers\n        printf(\"add-highlighter shared/clojure/code/ regex (?<!%s)[-+]?(?:0(?:[xX][0-9a-fA-F]+|[0-7]*)|[1-9]\\\\d*)N? 0:value\\n\", symbol_char);\n        printf(\"add-highlighter shared/clojure/code/ regex (?<!%s)[-+]?(?:0|[1-9]\\\\d*)(?:\\\\.\\\\d*)(?:M|[eE][-+]?\\\\d+)? 0:value\\n\", symbol_char);\n        printf(\"add-highlighter shared/clojure/code/ regex (?<!%s)[-+]?(?:0|[1-9]\\\\d*)/(?:0|[1-9]\\\\d*) 0:value\\n\", symbol_char);\n\n        print_word_highlighter(keywords, \"keyword\");\n        print_word_highlighter(core_fns, \"function\");\n        print_word_highlighter(core_vars, \"variable\");\n\n        printf(\"declare-option str-list clojure_static_words \")\n        print_static_words(keywords);\n        print_static_words(core_fns);\n        print_static_words(core_vars);\n        printf(\"\\n\");\n    }\nEOF\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden clojure-configure-window %{\n    set-option window static_words %opt{clojure_static_words}\n\n    hook window ModeChange pop:insert:.* -group clojure-trim-indent clojure-trim-indent\n    hook window InsertChar \\n -group clojure-indent clojure-indent-on-new-line\n\n    set-option buffer extra_word_chars '_' . / * ? + - < > ! : \"'\"\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window clojure-.+ }\n}\n\ndefine-command -hidden clojure-trim-indent lisp-trim-indent\n\ndeclare-option \\\n    -docstring 'regex matching the head of forms which have options *and* indented bodies' \\\n    regex clojure_special_indent_forms \\\n    '(?:def.*|doseq|for|fn\\*?|if(-.*|)|let.*|loop|ns|testing|with-.*|when(-.*|))'\n\ndefine-command -hidden clojure-indent-on-new-line %{\n    # registers: i = best align point so far; w = start of first word of form\n    evaluate-commands -draft -save-regs '/\"|^@iw' -itersel %{\n        execute-keys -draft 'gk\"iZ'\n        try %{\n            execute-keys -draft '[bl\"i<a-Z><gt>\"wZ'\n\n            try %{\n                # If a special form, indent another (indentwidth - 1) spaces\n                execute-keys -draft '\"wze<a-K>[\\s()\\[\\]\\{\\}]<ret><a-k>\\A' %opt{clojure_special_indent_forms} '\\z<ret>'\n                execute-keys -draft '\"wze<a-L>s.{' %sh{printf $(( kak_opt_indentwidth - 1 ))} '}\\K.*<ret><a-;>;\"i<a-Z><gt>'\n            } catch %{\n                # If not special and parameter appears on line 1, indent to parameter\n                execute-keys -draft '\"wz<a-K>[()[\\]{}]<ret>e<a-K>[\\s()\\[\\]\\{\\}]<ret><a-l>s\\h\\K[^\\s].*<ret><a-;>;\"i<a-Z><gt>'\n            }\n        }\n        try %{ execute-keys -draft '[rl\"i<a-Z><gt>' }\n        try %{ execute-keys -draft '[Bl\"i<a-Z><gt>' }\n        execute-keys -draft ';\"i<a-z>a&,'\n    }\n}\n\ndeclare-option -docstring %{\n    top-level directories which can contain clojure files\n    e.g. '(src|test|dev)'\n} regex clojure_source_directories '(src|test|dev)'\n\ndefine-command -docstring %{clojure-insert-ns: Insert namespace directive at top of Clojure source file} \\\n    clojure-insert-ns %{\n    evaluate-commands -draft %{\n        execute-keys -save-regs '' 'gk\\O' \"%val{bufname}\" '<esc>giZ'\n        try %{ execute-keys 'z<a-l>s\\.clj[csx]?$<ret><a-d>' }\n        try %{ execute-keys 'z<a-l>s^' \"%opt{clojure_source_directories}\" '/<ret><a-d>' }\n        try %{ execute-keys 'z<a-l>s/<ret>r.' }\n        try %{ execute-keys 'z<a-l>s_<ret>r-' }\n        execute-keys 'z<a-l>\\c(ns <c-r>\")<ret><esc>'\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/cmake.kak",
    "content": "hook global BufCreate .+\\.cmake|.*/CMakeLists.txt %{\n    set-option buffer filetype cmake\n}\n\nhook global BufCreate .*/CMakeCache.txt %{\n    set-option buffer filetype ini\n}\n\nhook global WinSetOption filetype=cmake %{\n    require-module cmake\n}\n\nhook -group cmake-highlight global WinSetOption filetype=cmake %{\n    add-highlighter window/cmake ref cmake\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/cmake }\n}\n\nprovide-module cmake %{\n\nadd-highlighter shared/cmake regions\nadd-highlighter shared/cmake/code default-region group\nadd-highlighter shared/cmake/comment  region '#' '$' fill comment\nadd-highlighter shared/cmake/argument region -recurse '\\(' '\\w+\\h*\\(\\K' '(?=\\))' regions\n\nadd-highlighter shared/cmake/code/ regex '\\w+\\h*(?=\\()' 0:meta\n\nadd-highlighter shared/cmake/argument/args default-region regex '\\$\\{\\w+\\}' 0:variable\nadd-highlighter shared/cmake/argument/comment  region '#' '$' fill comment\nadd-highlighter shared/cmake/argument/quoted region '\"' '(?<!\\\\)(\\\\\\\\)*\"' group\nadd-highlighter shared/cmake/argument/raw-quoted region -match-capture '\\[(=*)\\[' '\\](=*)\\]' ref cmake/argument/quoted\n\nadd-highlighter shared/cmake/argument/quoted/ fill string\nadd-highlighter shared/cmake/argument/quoted/ regex '\\$\\{\\w+\\}' 0:variable\nadd-highlighter shared/cmake/argument/quoted/ regex '\\w+\\h*(?=\\()' 0:function\n\n}\n"
  },
  {
    "path": "rc/filetype/coffee.kak",
    "content": "# http://coffeescript.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](coffee) %{\n    set-option buffer filetype coffee\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=coffee %{\n    require-module coffee\n\n    hook window ModeChange pop:insert:.* -group coffee-trim-indent coffee-trim-indent\n    hook window InsertChar \\n -group coffee-insert coffee-insert-on-new-line\n    hook window InsertChar \\n -group coffee-indent coffee-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window coffee-.+ }\n}\n\nhook -group coffee-highlight global WinSetOption filetype=coffee %{\n    add-highlighter window/coffee ref coffee\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/coffee }\n}\n\n\nprovide-module coffee %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/coffee regions\nadd-highlighter shared/coffee/code     default-region group\nadd-highlighter shared/coffee/single_string     region \"'\" \"'\"                    fill string\nadd-highlighter shared/coffee/single_string_alt region \"'''\" \"'''\"                fill string\nadd-highlighter shared/coffee/double_string     region '\"' (?<!\\\\)(\\\\\\\\)*\"        regions\nadd-highlighter shared/coffee/double_string_alt region '\"\"\"' '\"\"\"'                ref shared/coffee/double_string\nadd-highlighter shared/coffee/regex             region '/' (?<!\\\\)(\\\\\\\\)*/[gimy]* regions\nadd-highlighter shared/coffee/regex_alt         region '///' ///[gimy]*           ref shared/coffee/regex\nadd-highlighter shared/coffee/comment1          region '#' '$'                    fill comment\nadd-highlighter shared/coffee/comment2          region '###' '###'                fill comment\n\n# Regular expression flags are: g → global match, i → ignore case, m → multi-lines, y → sticky\n# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp\n\nadd-highlighter shared/coffee/double_string/base default-region fill string\nadd-highlighter shared/coffee/double_string/interpolation region -recurse \\{ \\Q#{ \\} fill meta\nadd-highlighter shared/coffee/regex/base default-region fill meta\nadd-highlighter shared/coffee/regex/interpolation region -recurse \\{ \\Q#{ \\} fill meta\n\n# Keywords are collected at\n# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords\n# http://coffeescript.org/documentation/docs/lexer.html#section-63\nadd-highlighter shared/coffee/code/ regex [$@]\\w* 0:variable\nadd-highlighter shared/coffee/code/ regex \\b(Array|Boolean|Date|Function|Number|Object|RegExp|String)\\b 0:type\nadd-highlighter shared/coffee/code/ regex \\b(document|false|no|null|off|on|parent|self|this|true|undefined|window|yes)\\b 0:value\nadd-highlighter shared/coffee/code/ regex \\b(and|is|isnt|not|or)\\b 0:operator\nadd-highlighter shared/coffee/code/ regex \\b(break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|function|if|implements|import|in|instanceof|interface|let|native|new|package|private|protected|public|return|static|super|switch|throw|try|typeof|var|void|while|with|yield)\\b 0:keyword\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden coffee-trim-indent %{\n    evaluate-commands -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden coffee-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy '#' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s '^\\h*\\K#\\h*' <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden coffee-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : coffee-trim-indent <ret> }\n        # indent after start structure\n        try %{ execute-keys -draft k x <a-k> ^ \\h * (case|catch|class|else|finally|for|function|if|switch|try|while|with) \\b | (=|->) $ <ret> j <a-gt> }\n    }\n}\n\n]\n"
  },
  {
    "path": "rc/filetype/conf.kak",
    "content": "hook global BufCreate .+\\.(repo|cfg|properties|desktop) %{\n    set-option buffer filetype conf\n}\n\nhook global WinCreate .+\\.ini %{\n    try %{\n        execute-keys /^\\h*#<ret>\n        set-option buffer filetype conf\n    }\n}\n\nhook global WinSetOption filetype=conf %{\n    require-module conf\n}\n\nhook -group conf-highlight global WinSetOption filetype=conf %{\n    add-highlighter window/conf ref conf\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/conf }\n}\n\nprovide-module conf %{\n\nadd-highlighter shared/conf regions\nadd-highlighter shared/conf/code default-region group\nadd-highlighter shared/conf/comment region '(^|\\h)\\K#' $ fill comment\n\nadd-highlighter shared/conf/code/ regex \"(?S)^\\h*(\\[.+?\\])\\h*$\" 1:title\nadd-highlighter shared/conf/code/ regex \"^\\h*([^\\[][^=\\n]*)=([^\\n]*)\" 1:variable 2:value\n\n}\n"
  },
  {
    "path": "rc/filetype/coq.kak",
    "content": "\n# Detection\n# --------\n\nhook global BufCreate .*\\.v %{\n    set-option buffer filetype coq\n}\n\n# Initialization\n# --------------\n\nhook global WinSetOption filetype=coq %{\n    require-module coq\n    hook window ModeChange pop:insert:.* -group coq-trim-indent coq-trim-indent\n    hook window InsertChar \\n -group coq-indent coq-copy-indent-on-newline\n\n    set-option window static_words %opt{coq_static_words}\n    add-highlighter window/coq ref coq\n\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-highlighter window/coq\n        remove-hooks window coq-indent \n    }\n}\n\nprovide-module coq %{\n\n# Syntax\n# ------\n\n# This is a `looks sensible' keyword syntax highlighting, far from being correct.\n# Note that only the core language and the proof language is supported,\n# the Ltac language is not (for now).\n     \nadd-highlighter shared/coq regions\n\nadd-highlighter shared/coq/comment region -recurse \\Q(* \\Q(* \\Q*) fill comment\nadd-highlighter shared/coq/string region (?<!\")\" (?<!\")(\"\")*\" fill string\n\nadd-highlighter shared/coq/command default-region group\n\n# This is not any lexical convention of coq, simply highlighting used to make\n# proofs look better, based on how people usually use notations\nadd-highlighter shared/coq/command/ regex [`!@#$%^&*-=+\\\\:\\;|<>/]+ 0:operator\nadd-highlighter shared/coq/command/ regex \\(dfs\\)|\\(bfs\\)          0:operator\nadd-highlighter shared/coq/command/ regex [()\\[\\]{}]               0:operator\n\n# numeral literals\nadd-highlighter shared/coq/command/ regex [-]?[0-9][0-9_]*(\\.[0-9_]+)?([eE][+-][0-9_]+)? 0:value\n\nevaluate-commands %sh{\n    # These are builtin keywords of the Gallina language (without tactics)\n    keywords=\"_ IF Prop SProp Set Type as at by cofix discriminated else end exists exists2 fix for\"\n    keywords=\"${keywords} forall fun if in lazymatch let match multimatch return then using where with\"\n    keywords=\"${keywords} inside outside\"\n\n    # These are (part of) coq top level commands\n    commands=\"Abort About Add Admitted All Arguments Axiom Back BackTo\"\n    commands=\"${commands} Canonical Cd Check Coercion CoFixpoint Collection Compute Conjecture Context Contextual Corollary\"\n    commands=\"${commands} Declare Defined Definition Delimit Drop End Eval Example Existential Export\"\n    commands=\"${commands} Fact Fail File Fixpoint Focus From Function Generalizable Global Goal Grab\"\n    commands=\"${commands} Hint Hypotheses Hypothesis Immediate Implicit Import Include Inductive\"\n    commands=\"${commands} Lemma Let Library Load LoadPath Local Locate Module No Notation Opaque\"\n    commands=\"${commands} Parameter Parameters Primitive Print Proof Property Proposition Pwd Qed Quit\"\n    commands=\"${commands} Rec Record Redirect Register Remark Remove Require Reset\"\n    commands=\"${commands} Section Search SearchAbout SearchHead SearchPattern SearchRewrite Show Strategy\"\n    commands=\"${commands} Test Theorem Time Timeout Transparent Types Universes Undo Unfocus Unfocused Unset Variable Variables\"\n\n    # These are (part of) coq's builtin tactics\n    tactics=\"abstract absurd admit all apply assert assert_fails\"\n    tactics=\"${tactics} assert_succeeds assumption auto autoapply\"\n    tactics=\"${tactics} autorewrite autounfold btauto by case cbn\"\n    tactics=\"${tactics} cbv change clear clearbody cofix compare\"\n    tactics=\"${tactics} compute congr congruence constructor contradict\"\n    tactics=\"${tactics} cut cutrewrite cycle decide decompose dependent\"\n    tactics=\"${tactics} destruct discriminate do done double\"\n    tactics=\"${tactics} eapply eassert eauto eexact elim elimtype exact exfalso\"\n    tactics=\"${tactics} fail field first firstorder fix fold functional\"\n    tactics=\"${tactics} generalize guard have hnf idtac induction injection\"\n    tactics=\"${tactics} instantiate intro intros intuition inversion\"\n    tactics=\"${tactics} inversion_clear lapply lazy last move omega\"\n    tactics=\"${tactics} pattern pose progress red refine reflexivity\"\n    tactics=\"${tactics} remember rename repeat replace rewrite right ring\"\n    tactics=\"${tactics} set setoid_reflexivity setoid_replace setoid_rewrite\"\n    tactics=\"${tactics} setoid_symmetry setoid_transitivity simpl simple\"\n    tactics=\"${tactics} simplify_eq solve specialize split start stop\"\n    tactics=\"${tactics} subst symmetry tauto transitivity trivial try\"\n    tactics=\"${tactics} under unfold unify unlock\"\n\n    echo declare-option str-list coq_static_words ${keywords} ${commands} ${tactics}\n\n    keywords_regex=$(echo ${keywords} | tr ' ' '|')\n    printf %s \"\n        add-highlighter shared/coq/command/ regex \\b(${keywords_regex})\\b 0:keyword\n    \"\n    commands_regex=$(echo ${commands} | tr ' ' '|')\n    printf %s \"\n        add-highlighter shared/coq/command/ regex ^[\\h\\n]*(${commands_regex})\\b 0:variable\n    \"\n\n    tactics_regex=$(echo ${tactics} | tr ' ' '|')\n    printf %s \"\n        add-highlighter shared/coq/command/ regex \\b(${tactics_regex})\\b 0:keyword\n    \"\n}\n\n# Indentation\n# -----------\n# Coq's syntax is based heavily on keywords and program structure,\n# not based on explicit, unique delimiters, like braces in C-family.\n# So it is difficult to properly indent using only regex...\n# Hence here only a simple mechanism of copying indent is done.\ndefine-command -hidden coq-copy-indent-on-newline %{\n    evaluate-commands -draft -itersel %{\n        try %{ execute-keys -draft k x s ^\\h+ <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden coq-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/crystal.kak",
    "content": "# Crystal\n# https://crystal-lang.org\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate '.*\\.cr' %{\n    set-option buffer filetype crystal\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=crystal %{\n    require-module crystal\n\n    add-highlighter window/crystal ref crystal\n    evaluate-commands set-option window static_words %opt{crystal_keywords} %opt{crystal_attributes} %opt{crystal_objects}\n\n    hook window ModeChange pop:insert:.* -group crystal-trim-indent crystal-trim-indent\n    hook window InsertChar .*   -group crystal-indent crystal-indent-on-char\n    hook window InsertChar '\\n' -group crystal-indent crystal-indent-on-new-line\n    hook window InsertChar '\\n' -group crystal-insert crystal-insert-on-new-line\n\n    hook -always -once window WinSetOption filetype=.* %{\n        remove-highlighter window/crystal\n        remove-hooks window crystal-.+\n    }\n}\n\nprovide-module crystal %§\n\ndeclare-option -hidden str-list crystal_keywords 'abstract' 'alias' 'annotation' 'as' 'asm' 'begin' 'break' 'case' 'class' 'def' 'do' 'else' 'elsif' 'end' 'ensure' 'enum' 'extend' 'false' 'for' 'fun' 'if' 'include' 'instance_sizeof' 'is_a?' 'lib' 'macro' 'module' 'next' 'nil' 'nil?' 'of' 'offsetof' 'out' 'pointerof' 'private' 'protected' 'require' 'rescue' 'responds_to?' 'return' 'select' 'self' 'sizeof' 'struct' 'super' 'then' 'true' 'type' 'typeof' 'uninitialized' 'union' 'unless' 'until' 'verbatim' 'when' 'while' 'with' 'yield'\n# https://crystal-lang.org/reference/syntax_and_semantics/methods_and_instance_variables.html#getters-and-setters\ndeclare-option -hidden str-list crystal_attributes 'getter' 'setter' 'property'\ndeclare-option -hidden str-list crystal_operators '+' '-' '*' '/' '//' '%' '|' '&' '^' '~' '**' '<<' '<' '<=' '==' '!=' '=~' '!~' '>>' '>' '>=' '<=>' '===' '[]' '[]=' '[]?' '[' '&+' '&-' '&*' '&**'\ndeclare-option -hidden str-list crystal_objects 'Adler32' 'ArgumentError' 'Array' 'Atomic' 'Base64' 'Benchmark' 'BigDecimal' 'BigFloat' 'BigInt' 'BigRational' 'BitArray' 'Bool' 'Box' 'Bytes' 'Channel' 'Char' 'Class' 'Colorize' 'Comparable' 'Complex' 'Concurrent' 'ConcurrentExecutionException' 'CRC32' 'Crypto' 'Crystal' 'CSV' 'Debug' 'Deprecated' 'Deque' 'Digest' 'Dir' 'DivisionByZeroError' 'DL' 'ECR' 'Enum' 'Enumerable' 'ENV' 'Errno' 'Exception' 'Fiber' 'File' 'FileUtils' 'Flags' 'Flate' 'Float' 'Float32' 'Float64' 'GC' 'Gzip' 'Hash' 'HTML' 'HTTP' 'Indexable' 'IndexError' 'INI' 'Int' 'Int128' 'Int16' 'Int32' 'Int64' 'Int8' 'InvalidBigDecimalException' 'InvalidByteSequenceError' 'IO' 'IPSocket' 'Iterable' 'Iterator' 'JSON' 'KeyError' 'Levenshtein' 'Link' 'LLVM' 'Logger' 'Markdown' 'Math' 'MIME' 'Mutex' 'NamedTuple' 'Nil' 'NilAssertionError' 'NotImplementedError' 'Number' 'OAuth' 'OAuth2' 'Object' 'OpenSSL' 'OptionParser' 'OverflowError' 'PartialComparable' 'Path' 'Pointer' 'PrettyPrint' 'Proc' 'Process' 'Random' 'Range' 'Readline' 'Reference' 'Reflect' 'Regex' 'SemanticVersion' 'Set' 'Signal' 'Slice' 'Socket' 'Spec' 'StaticArray' 'String' 'StringPool' 'StringScanner' 'Struct' 'Symbol' 'System' 'TCPServer' 'TCPSocket' 'Termios' 'Time' 'Tuple' 'TypeCastError' 'UDPSocket' 'UInt128' 'UInt16' 'UInt32' 'UInt64' 'UInt8' 'Unicode' 'Union' 'UNIXServer' 'UNIXSocket' 'URI' 'UUID' 'VaList' 'Value' 'WeakRef' 'XML' 'YAML' 'Zip' 'Zlib'\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/crystal regions\nadd-highlighter shared/crystal/code default-region group\n\n# Comments\n# https://crystal-lang.org/reference/syntax_and_semantics/comments.html\n# Avoid string literals with interpolation\nadd-highlighter shared/crystal/comment region '#(?!\\{)' '$' fill comment\n\n# String\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/string.html\nadd-highlighter shared/crystal/string region '\"' '(?<!\\\\)(\\\\\\\\)*\"' regions\n\n# Percent string literals\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/string.html#percent-string-literals\nadd-highlighter shared/crystal/parenthesis-string region -recurse '\\(' '%Q?\\(' '\\)' regions\nadd-highlighter shared/crystal/bracket-string     region -recurse '\\[' '%Q?\\[' '\\]' regions\nadd-highlighter shared/crystal/brace-string       region -recurse '\\{' '%Q?\\{' '\\}' regions\nadd-highlighter shared/crystal/angle-string       region -recurse '<' '%Q?<' '>'    regions\nadd-highlighter shared/crystal/pipe-string        region          '%Q?\\|' '\\|'      regions\n\n# Raw\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/string.html#percent-string-literals\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/string.html#percent-string-array-literal\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/symbol.html#percent-symbol-array-literal\nadd-highlighter shared/crystal/raw-parenthesis-string region -recurse '\\(' '%[qwi]\\(' '\\)' fill string\nadd-highlighter shared/crystal/raw-bracket-string     region -recurse '\\[' '%[qwi]\\[' '\\]' fill string\nadd-highlighter shared/crystal/raw-brace-string       region -recurse '\\{' '%[qwi]\\{' '\\}' fill string\nadd-highlighter shared/crystal/raw-angle-string       region -recurse '<' '%[qwi]<' '>'    fill string\nadd-highlighter shared/crystal/raw-pipe-string        region          '%[qwi]\\|' '\\|'      fill string\n\n# Here document\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/string.html#heredoc\nadd-highlighter shared/crystal/heredoc region -match-capture '<<-(\\w+)' '^\\h*(\\w+)$' regions\n# Raw\nadd-highlighter shared/crystal/raw-heredoc region -match-capture \"<<-'(\\w+)'\" '^\\h*(\\w+)$' regions\nadd-highlighter shared/crystal/raw-heredoc/fill default-region fill string\nadd-highlighter shared/crystal/raw-heredoc/interpolation region -recurse '\\{' '#\\{' '\\}' fill meta\n\n# Symbol\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/symbol.html\nadd-highlighter shared/crystal/quoted-symbol region ':\"' '(?<!\\\\)(\\\\\\\\)*\"' fill value\n\n# Regular expressions\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/regex.html\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/regex.html#modifiers\nadd-highlighter shared/crystal/regex region '/' '(?<!\\\\)(\\\\\\\\)*/[imx]*' regions\n# Avoid unterminated regular expression\nadd-highlighter shared/crystal/division region ' / ' '.\\K' group\n\n# Percent regex literals\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/regex.html#percent-regex-literals\nadd-highlighter shared/crystal/parenthesis-regex region -recurse '\\(' '%r?\\(' '\\)[imx]*' regions\nadd-highlighter shared/crystal/bracket-regex region -recurse '\\[' '%r?\\[' '\\][imx]*' regions\nadd-highlighter shared/crystal/brace-regex region -recurse '\\{' '%r?\\{' '\\}[imx]*' regions\nadd-highlighter shared/crystal/angle-regex region -recurse '<' '%r?<' '>[imx]*' regions\nadd-highlighter shared/crystal/pipe-regex region '%r?\\|' '\\|[imx]*' regions\n\n# Command\n# https://crystal-lang.org/reference/syntax_and_semantics/literals/command.html\nadd-highlighter shared/crystal/command region '`' '(?<!\\\\)(\\\\\\\\)*`' regions\n\n# Percent command literals\nadd-highlighter shared/crystal/parenthesis-command region -recurse '\\(' '%x?\\(' '\\)' regions\nadd-highlighter shared/crystal/bracket-command region -recurse '\\[' '%x?\\[' '\\]' regions\nadd-highlighter shared/crystal/brace-command region -recurse '\\{' '%x?\\{' '\\}' regions\nadd-highlighter shared/crystal/angle-command region -recurse '<' '%x?<' '>' regions\nadd-highlighter shared/crystal/pipe-command region '%x?\\|' '\\|' regions\n\nevaluate-commands %sh[\n    # Keywords\n    eval \"set -- $kak_quoted_opt_crystal_keywords\"\n    regex=\"\\\\b(?:\\\\Q$1\\\\E\"\n    shift\n    for keyword do\n        regex=\"$regex|\\\\Q$keyword\\\\E\"\n    done\n    regex=\"$regex)\\\\b\"\n    printf 'add-highlighter shared/crystal/code/keywords regex %s 0:keyword\\n' \"$regex\"\n\n    # Attributes\n    eval \"set -- $kak_quoted_opt_crystal_attributes\"\n    regex=\"\\\\b(?:\\\\Q$1\\\\E\"\n    shift\n    for attribute do\n        regex=\"$regex|\\\\Q$attribute\\\\E\"\n    done\n    regex=\"$regex)\\\\b\"\n    printf 'add-highlighter shared/crystal/code/attributes regex %s 0:attribute\\n' \"$regex\"\n\n    # Symbols\n    eval \"set -- $kak_quoted_opt_crystal_operators\"\n    # Avoid to match modules\n    regex=\"(?<!:):(?:\\\\w+[?!]?\"\n    for operator do\n        regex=\"$regex|\\\\Q$operator\\\\E\"\n    done\n    regex=\"$regex)\"\n    printf 'add-highlighter shared/crystal/code/symbols regex %%(%s) 0:value\\n' \"$regex\"\n\n    # Objects\n    eval \"set -- $kak_quoted_opt_crystal_objects\"\n    regex=\"\\\\b(?:\\\\Q$1\\\\E\"\n    shift\n    for object do\n        regex=\"$regex|\\\\Q$object\\\\E\"\n    done\n    regex=\"$regex)\\\\b\"\n    printf 'add-highlighter shared/crystal/code/objects regex %s 0:builtin\\n' \"$regex\"\n\n    # Interpolation\n    # String\n    # https://crystal-lang.org/reference/syntax_and_semantics/literals/string.html#interpolation\n    for id in string parenthesis-string bracket-string brace-string angle-string pipe-string heredoc; do\n        printf \"\n            add-highlighter shared/crystal/$id/fill default-region fill string\n            add-highlighter shared/crystal/$id/interpolation region -recurse '\\\\{' '#\\\\{' '\\\\}' ref crystal\n        \"\n    done\n\n    # Regular expressions\n    # https://crystal-lang.org/reference/syntax_and_semantics/literals/regex.html#interpolation\n    for id in regex parenthesis-regex bracket-regex brace-regex angle-regex pipe-regex; do\n        printf \"\n            add-highlighter shared/crystal/$id/fill default-region fill meta\n            add-highlighter shared/crystal/$id/interpolation region -recurse '\\\\{' '#\\\\{' '\\\\}' ref crystal\n        \"\n    done\n\n    # Command\n    for id in command parenthesis-command bracket-command brace-command angle-command pipe-command; do\n        printf \"\n            add-highlighter shared/crystal/$id/fill default-region fill meta\n            add-highlighter shared/crystal/$id/interpolation region -recurse '\\\\{' '#\\\\{' '\\\\}' ref crystal\n        \"\n    done\n]\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden crystal-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h+$ <ret> d }\n    }\n}\n\ndefine-command -hidden crystal-indent-on-char %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # align 'else' to 'if/case'\n        try %{ execute-keys -draft x <a-k> ^\\h*else$   <ret> <a-a>i <a-semicolon> <a-?> ^\\h*(?:if|case)                                               <ret> <a-S> 1<a-&> }\n        # align 'elsif' to 'if'\n        try %{ execute-keys -draft x <a-k> ^\\h*elsif$  <ret> <a-a>i <a-semicolon> <a-?> ^\\h*(?:if)                                                    <ret> <a-S> 1<a-&> }\n        # align 'when' to 'case'\n        try %{ execute-keys -draft x <a-k> ^\\h*when$   <ret> <a-a>i <a-semicolon> <a-?> ^\\h*(?:case)                                                  <ret> <a-S> 1<a-&> }\n        # align 'rescue' to 'begin/def'\n        try %{ execute-keys -draft x <a-k> ^\\h*rescue$ <ret> <a-a>i <a-semicolon> <a-?> ^\\h*(?:begin|def)                                             <ret> <a-S> 1<a-&> }\n        # align 'end' to opening structure\n        try %{ execute-keys -draft x <a-k> ^\\h*end$    <ret> <a-a>i <a-semicolon> <a-?> ^\\h*(?:begin|case|class|def|for|if|module|unless|until|while) <ret> <a-S> 1<a-&> }\n    }\n}\n\ndefine-command -hidden crystal-indent-on-new-line %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # Copy previous line indent\n        try %{ execute-keys -draft K <a-&> }\n        # Remove previous line's trailing spaces\n        try %{ execute-keys -draft k :crystal-trim-indent <ret> }\n        # Indent after start structure/opening statement\n        try %{ execute-keys -draft k x <a-k> ^\\h*(?:begin|case|class|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|while|.+\\bdo$|.+\\bdo\\h\\|.+(?=\\|))[^0-9A-Za-z_!?] <ret> j <a-gt> }\n    }\n}\n\ndefine-command -hidden crystal-insert-on-new-line %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # copy _#_ comment prefix and following white spaces\n        try %{ execute-keys -draft k x s '^\\h*\\K#\\h*' <ret> y j x<semicolon> P }\n        # wisely add end structure\n        evaluate-commands -save-regs x %[\n            try %{ execute-keys -draft k x s ^ \\h + <ret> \\\" x y } catch %{ reg x '' }\n            try %[\n                evaluate-commands -draft %[\n                    # Check if previous line opens a block\n                    execute-keys -draft kx <a-k>^<c-r>x(?:begin|case|class|def|for|if|module|unless|until|while|.+\\bdo$|.+\\bdo\\h\\|.+(?=\\|))[^0-9A-Za-z_!?]<ret>\n                    # Check that we do not already have an end for this indent level which is first set via `crystal-indent-on-new-line` hook\n                    execute-keys -draft }i J x <a-K> ^<c-r>x(?:end|else|elsif|rescue|when)[^0-9A-Za-z_!?]<ret>\n                ]\n                execute-keys -draft o<c-r>xend<esc> # insert a new line with containing end\n            ]\n        ]\n    ]\n]\n\ndefine-command -hidden crystal-fetch-keywords %{\n    set-register dquote %sh{\n        curl --location https://github.com/crystal-lang/crystal/raw/master/src/compiler/crystal/syntax/lexer.cr |\n        kak -f '%1scheck_ident_or_keyword\\(:(\\w+\\??), \\w+\\)<ret>y%<a-R>a<ret><esc><a-_>a<del><esc>|sort<ret>'\n    }\n}\n\ndefine-command -hidden crystal-fetch-operators %{\n    set-register dquote %sh{\n        curl --location https://github.com/crystal-lang/crystal/raw/master/src/compiler/crystal/syntax/parser.cr |\n        kak -f '/AtomicWithMethodCheck =<ret>x1s:\"([^\"]+)\"<ret>y%<a-R>i''<esc>a''<ret><esc><a-_>a<del><esc>'\n    }\n}\n\ndefine-command -hidden crystal-fetch-objects %{\n    set-register dquote %sh{\n        curl --location https://crystal-lang.org/api/ |\n        # Remove Top Level Namespace\n        kak -f '%1sdata-id=\"github.com/crystal-lang/crystal/(\\w+)\"<ret>)<a-,>y%<a-R>a<ret><esc><a-_>a<del><esc>'\n    }\n}\n\n§\n"
  },
  {
    "path": "rc/filetype/css.kak",
    "content": "# http://w3.org/Style/CSS\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](css) %{\n    set-option buffer filetype css\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=css %[\n    require-module css\n\n    hook window ModeChange pop:insert:.* -group css-trim-indent css-trim-indent\n    hook window InsertChar \\n -group css-insert css-insert-on-new-line\n    hook window InsertChar \\n -group css-indent css-indent-on-new-line\n    hook window InsertChar \\} -group css-indent css-indent-on-closing-curly-brace\n    set-option buffer extra_word_chars '_' '-'\n    set-face global cssLogicalOperator +i@keyword\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window css-.+ }\n]\n\nhook -group css-highlight global WinSetOption filetype=css %{\n    add-highlighter window/css ref css\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/css }\n}\n\n\nprovide-module css %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\nadd-highlighter shared/css regions\nadd-highlighter shared/css/code default-region group\nadd-highlighter shared/css/attr_selector region \\[ \\] regions\nadd-highlighter shared/css/double_string region '\"'  (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/css/single_string region \"'\"  (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/css/comment    region /\\* \\*/ fill comment\n\nevaluate-commands %sh{\n  # html tags\n  # generated from the URL & <code> below\n  # includes elements that cannot be styled, which is fine.\n  # \n  # https://developer.mozilla.org/en-US/docs/Web/HTML/Element\n  html_tags='html body address article aside footer header h1 h2 h3 h4 h5 h6 main nav section blockquote dd div dl dt figcaption figure hr li ol p pre ul a abbr b bdi bdo br cite code data dfn em i kbd mark q rp rt ruby s samp small span strong sub sup time u var wbr area audio img map track video embed iframe object param picture portal source canvas noscript script del ins caption col colgroup table tbody td tfoot th thead tr button datalist fieldset form input label legend meter optgroup option output progress select textarea details dialog menu summary slot template acronym applet basefont bgsound big blink center content dir font frame frameset hgroup image keygen marquee menuitem nobr noembed noframes plaintext rb rtc shadow spacer strike tt xmp'\n\n  # Units\n  # ‾‾‾‾‾\n  # generated from the URL & <code> below\n  # includes #rgb, #rrggbb, #rrggbbaa as color values {3,8}\n  # \n  # https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Values_and_Units\n  units='% cap ch cm deg dpcm dpi dppx em ex grad Hz ic in kHz lh mm ms pc pt px Q rad rem rlh s turn vb vh vi vmax vmin vw x'\n\n  logical_ops='and not only from to'\n  keywords='!important auto inherit initial unset none'\n  media_types='all print screen speech'\n\n  # easing_re='linear|ease(-(in-out|in|out))?|step-(start|end)'\n\n  # combinators='+ > ~ ||'\n  # attribute_op='= ~= |= ^= $= *='\n\n  join() { eval set -- $1; IFS=\"|\"; echo \"$*\"; }\n\n  # Selectors\n  # ‾‾‾‾‾‾‾‾‾\n  # universal: *, ns|*, *|*, |*\n  # class/id: .class, #id\n  # type: element\n  # attr: [attr=val]\n\n  # order below matters\n  printf %s \"\n  add-highlighter shared/css/code/tag_selectors regex \\b($(join \"${html_tags}\"))((:[a-z:])|[\\h.#,]) 1:keyword\n\n  add-highlighter shared/css/code/functional_notation regex ([a-zA-Z0-9-_]+[a-zA-Z0-9])\\( 1:keyword\n\n  add-highlighter shared/css/code/logical_operators regex (\\b($(join \"${logical_ops}\"))\\b|$(join \"${keywords}\")) 1:cssLogicalOperator\n\n  add-highlighter shared/css/code/media_types regex \\b($(join \"${media_types}\"))\\b 1:+i\n\n  # (after functional notation as they may contain paranthesis)\n  add-highlighter shared/css/code/pseudo regex (:{1,2})([a-z-]+) 0:attribute\n\n  add-highlighter shared/css/code/at_rules regex @[a-z-]+ 0:function \n\n  add-highlighter shared/css/code/css_property regex ([A-Za-z][A-Za-z0-9_-]*)\\h*:\\h 1:operator 1:+a\n\n  add-highlighter shared/css/code/selectors regex (\\*|[*]?[.][A-Za-z][A-Za-z0-9_-]+) 0:type\n  add-highlighter shared/css/code/selectors_id regex (\\*|[*]?[#][A-Za-z][A-Za-z0-9_-]+) 0:type 0:+i\n\n  add-highlighter shared/css/code/hex_values regex (#[0-9A-Fa-f]{3,8})\\b 0:value 0:+a\n  add-highlighter shared/css/code/units regex \\b(\\d*\\.)?\\d+($(join \"${units}\"))?\\b 0:value 0:+a\n  \"\n}\n\n# Attribute Selectors\nadd-highlighter shared/css/attr_selector/base default-region group\nadd-highlighter shared/css/attr_selector/base/ regex ([a-zA-Z0-9-]+) 1:attribute\nadd-highlighter shared/css/attr_selector/base/ regex \\h(i|s) 1:type\nadd-highlighter shared/css/attr_selector/double_string region '\"'  (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/css/attr_selector/single_string region \"'\"  (?<!\\\\)(\\\\\\\\)*' fill string\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden css-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden css-indent-on-new-line %[\n    evaluate-commands -draft -itersel %[\n        execute-keys <semicolon>\n        try %<\n            # if previous line is part of a comment, do nothing\n            execute-keys -draft <a-?>/\\*<ret> <a-K>^\\h*[^/*\\h]<ret>\n        > catch %<\n            # else if previous line closed a paren (possibly followed by words and a comment),\n            # copy indent of the opening paren line\n            execute-keys -draft kx 1s(\\))(\\h+\\w+)*\\h*(\\;\\h*)?(?://[^\\n]+)?\\n\\z<ret> m<a-semicolon>J <a-S> 1<a-&>\n        > catch %<\n            # else indent new lines with the same level as the previous one\n            execute-keys -draft K <a-&>\n        >\n        # filter previous line\n        try %< execute-keys -draft k x <a-k>^\\h+$<ret> Hd >\n        # indent after lines ending with with {\n        try %[ execute-keys -draft k x <a-k> \\{$ <ret> j <a-gt> ]\n        # deindent closing brace when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> ]\n    ]\n]\n\ndefine-command -hidden css-insert-on-new-line %[\n    evaluate-commands -draft -itersel %<\n    execute-keys <semicolon>\n    try %[\n        # if the previous line isn't within a comment scope, break\n        execute-keys -draft kx <a-k>^(\\h*/\\*|\\h+\\*(?!/))<ret>\n\n        # find comment opening, validate it was not closed, and check its using star prefixes\n        execute-keys -draft <a-?>/\\*<ret><a-H> <a-K>\\*/<ret> <a-k>\\A\\h*/\\*([^\\n]*\\n\\h*\\*)*[^\\n]*\\n\\h*.\\z<ret>\n\n        try %[\n            # if the previous line is opening the comment, insert star preceeded by space\n            execute-keys -draft kx<a-k>^\\h*/\\*<ret>\n            execute-keys -draft i*<space><esc>\n        ] catch %[\n           try %[\n                # if the next line is a comment line insert a star\n                execute-keys -draft jx<a-k>^\\h+\\*<ret>\n                execute-keys -draft i*<space><esc>\n            ] catch %[\n                try %[\n                    # if the previous line is an empty comment line, close the comment scope\n                    execute-keys -draft kx<a-k>^\\h+\\*\\h+$<ret> x1s\\*(\\h*)<ret>c/<esc>\n                ] catch %[\n                    # if the previous line is a non-empty comment line, add a star\n                    execute-keys -draft i*<space><esc>\n                ]\n            ]\n        ]\n\n        # trim trailing whitespace on the previous line\n        try %[ execute-keys -draft s\\h+$<ret> d ]\n        # align the new star with the previous one\n        execute-keys Kx1s^[^*]*(\\*)<ret>&\n    ]\n    >\n]\n\ndefine-command -hidden css-indent-on-closing-curly-brace %[\n    evaluate-commands -draft -itersel %[\n        # align to opening curly brace when alone on a line\n        try %[ execute-keys -draft <a-h> <a-k> ^\\h+\\}$ <ret> m s \\A|.\\z <ret> 1<a-&> ]\n    ]\n]\n\n]\n"
  },
  {
    "path": "rc/filetype/cucumber.kak",
    "content": "# http://cukes.info\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](feature|story) %{\n    set-option buffer filetype cucumber\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=cucumber %{\n    require-module cucumber\n\n    hook window ModeChange pop:insert:.* -group cucumber-trim-indent cucumber-trim-indent\n    hook window InsertChar \\n -group cucumber-insert cucumber-insert-on-new-line\n    hook window InsertChar \\n -group cucumber-indent cucumber-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window cucumber-.+ }\n}\n\nhook -group cucumber-highlight global WinSetOption filetype=cucumber %{\n    add-highlighter window/cucumber ref cucumber\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/cucumber }\n}\n\n\nprovide-module cucumber %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/cucumber regions\nadd-highlighter shared/cucumber/code default-region group\nadd-highlighter shared/cucumber/language region ^\\h*#\\h*language: $ group\nadd-highlighter shared/cucumber/comment  region ^\\h*#             $ fill comment\n\nadd-highlighter shared/cucumber/language/ fill meta\nadd-highlighter shared/cucumber/language/ regex \\S+$ 0:value\n\n# Spoken languages\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n# https://github.com/cucumber/cucumber/wiki/Spoken-languages\n#\n# curl --location https://github.com/cucumber/gherkin/raw/master/lib/gherkin/i18n.json\n#\n# {\n#   \"en\": {\n#     \"name\": \"English\",\n#     \"native\": \"English\",\n#     \"feature\": \"Feature|Business Need|Ability\",\n#     \"background\": \"Background\",\n#     \"scenario\": \"Scenario\",\n#     \"scenario_outline\": \"Scenario Outline|Scenario Template\",\n#     \"examples\": \"Examples|Scenarios\",\n#     \"given\": \"*|Given\",\n#     \"when\": \"*|When\",\n#     \"then\": \"*|Then\",\n#     \"and\": \"*|And\",\n#     \"but\": \"*|But\"\n#   },\n#   …\n# }\n#\n# jq 'with_entries({ key: .key, value: .value | del(.name) | del(.native) | join(\"|\") })'\n#\n# {\n#   \"en\": \"Feature|Business Need|Ability|Background|Scenario|Scenario Outline|Scenario Template|Examples|Scenarios|*|Given|*|When|*|Then|*|And|*|But\",\n#   …\n# }\n\nadd-highlighter shared/cucumber/code/ regex \\b(Feature|Business\\h+Need|Ability|Background|Scenario|Scenario\\h+Outline|Scenario\\h+Template|Examples|Scenarios|Given|When|Then|And|But)\\b 0:keyword\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden cucumber-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden cucumber-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy '#' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden cucumber-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : cucumber-trim-indent <ret> }\n        # indent after lines containing :\n        try %{ execute-keys -draft , k x <a-k> : <ret> j <a-gt> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/cue.kak",
    "content": "# https://cuelang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](cue) %{\n    set-option buffer filetype cue\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=cue %[\n    require-module cue\n\n    # cleanup trailing whitespaces when exiting insert mode\n    hook window ModeChange pop:insert:.* -group cue-trim-indent cue-trim-indent\n    hook window InsertChar \\n -group cue-insert cue-insert-on-new-line\n    hook window InsertChar \\n -group cue-indent cue-indent-on-new-line\n    hook window InsertChar \\{ -group cue-indent cue-indent-on-opening-curly-brace\n    hook window InsertChar [)}] -group cue-indent cue-indent-on-closing\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window cue-.+ }\n]\n\nhook -group cue-highlight global WinSetOption filetype=cue %{\n    add-highlighter window/cue ref cue\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/cue }\n}\n\nprovide-module cue %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\n# https://cuelang.org/docs/references/spec/\n\nadd-highlighter shared/cue regions\nadd-highlighter shared/cue/code default-region group\nadd-highlighter shared/cue/simple_string    region '\"'   (?<!\\\\)(\\\\\\\\)*\" regions\nadd-highlighter shared/cue/simple_bytes     region \"'\"   (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/cue/multiline_string region '\"\"\"' '\"\"\"'           ref shared/cue/simple_string\nadd-highlighter shared/cue/multiline_bytes  region \"'''\" \"'''\"           ref shared/cue/simple_bytes\nadd-highlighter shared/cue/line_comment     region \"//\"  \"$\"             fill comment\n\nadd-highlighter shared/cue/simple_string/base default-region fill string\nadd-highlighter shared/cue/simple_string/interpolation region -recurse \\( \\\\\\( \\) fill meta\n\nevaluate-commands %sh{\n    # Grammar\n    binary_digit=\"[01]\"\n    decimal_digit=\"[0-9]\"\n    hex_digit=\"[0-9a-fA-F]\"\n    octal_digit=\"[0-7]\"\n\n    decimal_lit=\"[1-9](_?${decimal_digit})*\"\n    binary_lit=\"0b${binary_digit}(_?${binary_digit})*\"\n    hex_lit=\"0[xX]${hex_digit}(_?${hex_digit})*\"\n    octal_lit=\"0o${octal_digit}(_?${octal_digit})*\"\n\n    decimals=\"${decimal_digit}(_?${decimal_digit})*\"\n    multiplier=\"([KMGTP]i?)?\"\n    exponent=\"([eE][+-]?${decimals})\"\n    si_lit=\"(${decimals}(\\.${decimals})?${multiplier}|\\.${decimals}${multiplier})\"\n\n    int_lit=\"\\b(${decimal_lit}|${si_lit}|${octal_lit}|${binary_lit}|${hex_lit})\\b\"\n    float_lit=\"\\b${decimals}\\.(${decimals})?${exponent}?|\\b${decimals}${exponent}\\b|\\.${decimals}${exponent}?\\b\"\n\n    operator_chars=\"(\\+|-|\\*|/|&|&&|\\||\\|\\||=|==|=~|!|!=|!~|<|>|<=|>=)\"\n    punctuation=\"(_\\|_|:|::|,|\\.|\\.\\.\\.|\\(|\\{|\\[|\\)|\\}|\\])\"\n\n    function_calls=\"\\w+(?=\\()\"\n    identifier=\"(?!\\d)[\\w_$]+\"\n    reserved=\"\\b__${identifier}\"\n\n    preamble=\"^(package|import)\\b\"\n\n    functions=\"len close and or\"\n    keywords=\"for in if let\"\n    operators=\"div mod quo rem\"\n    types=\"\n        bool string bytes rune number\n        uint uint8 uint16 uint32 uint64 uint128\n        int int8 int16 int32 int64 int128\n        float float32 float64\n    \"\n    values=\"null true false\"\n\n    join() { sep=$2; set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    static_words=\"$(join \"package import ${functions} ${keywords} ${operators} ${types} ${values}\" ' ')\"\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list cue_static_words ${static_words}\"\n\n    functions=\"$(join \"${functions}\" '|')\"\n    keywords=\"$(join \"${keywords}\" '|')\"\n    operators=\"$(join \"${operators}\" '|')\"\n    types=\"$(join \"${types}\" '|')\"\n    values=\"$(join \"${values}\" '|')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/cue/code/ regex ${float_lit} 0:value\n        add-highlighter shared/cue/code/ regex ${function_calls} 0:function\n        add-highlighter shared/cue/code/ regex ${int_lit} 0:value\n        add-highlighter shared/cue/code/ regex ${operator_chars} 0:operator\n        add-highlighter shared/cue/code/ regex ${preamble} 0:keyword\n        add-highlighter shared/cue/code/ regex ${punctuation} 0:operator\n        add-highlighter shared/cue/code/ regex ${reserved} 0:keyword\n        add-highlighter shared/cue/code/ regex \\b(${functions})\\b 0:builtin\n        add-highlighter shared/cue/code/ regex \\b(${keywords})\\b 0:keyword\n        add-highlighter shared/cue/code/ regex \\b(${operators})\\b 0:operator\n        add-highlighter shared/cue/code/ regex \\b(${types})\\b 0:type\n        add-highlighter shared/cue/code/ regex \\b(${values})\\b 0:value\n    \"\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden cue-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden cue-insert-on-new-line %~\n    evaluate-commands -draft -itersel %<\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K//[!/]?\\h* <ret> y<c-o>P<esc> }\n    >\n~\n\ndefine-command -hidden cue-indent-on-new-line %~\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        try %<\n            # only if we didn't copy a comment\n            execute-keys -draft x <a-K> ^\\h*// <ret>\n            # indent after lines ending with { or (\n            try %[ execute-keys -draft k x <a-k> [{(]\\h*$ <ret> j <a-gt> ]\n            # indent after lines ending with [{(].+ and move first parameter to own line\n            try %< execute-keys -draft [c[({],[)}] <ret> <a-k> \\A[({][^\\n]+\\n[^\\n]*\\n?\\z <ret> L i<ret><esc> <gt> <a-S> <a-&> >\n            # deindent closing brace(s) when after cursor\n            try %< execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret>  m <a-S> 1<a-&> >\n        >\n        # filter previous line\n        try %{ execute-keys -draft k : cue-trim-indent <ret> }\n    >\n~\n\ndefine-command -hidden cue-indent-on-opening-curly-brace %[\n    evaluate-commands -draft -itersel %_\n        # align indent with opening paren when { is entered on a new line after the closing paren\n        try %[ execute-keys -draft h <a-F> ) M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n    _\n]\n\ndefine-command -hidden cue-indent-on-closing %[\n    evaluate-commands -draft -itersel %_\n        # align to opening curly brace or paren when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h*[)}]$ <ret> h m <a-S> 1<a-&> >\n    _\n]\n\n§\n"
  },
  {
    "path": "rc/filetype/d.kak",
    "content": "# http://dlang.org/\n#\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.di? %{\n    set-option buffer filetype d\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=d %{\n    require-module d\n\n    set-option window static_words %opt{d_static_words}\n\n    # cleanup trailing whitespaces when exiting insert mode\n    hook window ModeChange pop:insert:.* -group d-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n    hook window InsertChar \\n -group d-insert d-insert-on-new-line\n    hook window InsertChar \\n -group d-indent d-indent-on-new-line\n    hook window InsertChar \\{ -group d-indent d-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group d-indent d-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window d-.+ }\n}\n\nhook -group d-highlight global WinSetOption filetype=d %{\n    add-highlighter window/d ref d\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/d }\n}\n\nprovide-module d %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/d regions\nadd-highlighter shared/d/code default-region group\nadd-highlighter shared/d/string region %{(?<!')(?<!'\\\\)\"} %{(?<!\\\\)(?:\\\\\\\\)*\"} group\nadd-highlighter shared/d/verbatim_string region %{(?<!')(?<!'\\\\)`} %{(?<!\\\\)(?:\\\\\\\\)*`} fill meta\nadd-highlighter shared/d/verbatim_string_prefixed region %{r`([^(]*)\\(} %{\\)([^)]*)`} fill meta\nadd-highlighter shared/d/docstring1 region -recurse '/\\+' '/\\+\\+' '\\+/' fill documentation\nadd-highlighter shared/d/docstring2 region '/\\*\\*' '\\*/' fill documentation\nadd-highlighter shared/d/docstring3 region /// $ fill documentation\nadd-highlighter shared/d/disabled region -recurse '/\\+' '/\\+[^+]?' '\\+/' fill comment\nadd-highlighter shared/d/comment1 region '/\\*[^*]?' '\\*/' fill comment\nadd-highlighter shared/d/comment2 region '//[^/]?' $ fill comment\n\nadd-highlighter shared/d/string/ fill string\nadd-highlighter shared/d/string/ regex %{\\\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})\\b} 0:value\nadd-highlighter shared/d/code/ regex %{'((\\\\.)?|[^'\\\\])'} 0:value\nadd-highlighter shared/d/code/ regex \"-?([0-9_]*\\.(?!0[xXbB]))?\\b([0-9_]+|0[xX][0-9a-fA-F_]*\\.?[0-9a-fA-F_]+|0[bb][01_]+)([ep]-?[0-9_]+)?[fFlLuUi]*\\b\" 0:value\nadd-highlighter shared/d/code/ regex \"\\b(this)\\b\\s*[^(]\" 1:value\nadd-highlighter shared/d/code/ regex \"((?:~|\\b)this)\\b\\s*\\(\" 1:function\nadd-highlighter shared/d/code/ regex '(#line)\\h+(\\d+)(\\h+\"[^\"\\n]*\")?' 1:meta 2:value 3:string\n\nevaluate-commands %sh{\n    # Grammar\n\n    keywords=\"abstract|alias|align|asm|assert|auto|body|break|case|cast\"\n    keywords=\"${keywords}|catch|cent|class|const|continue|debug\"\n    keywords=\"${keywords}|default|delegate|delete|deprecated|do|else|enum|export|extern\"\n    keywords=\"${keywords}|final|finally|for|foreach|foreach_reverse|function|goto\"\n    keywords=\"${keywords}|if|immutable|import|in|inout|interface|invariant\"\n    keywords=\"${keywords}|is|lazy|macro|mixin|module|new|nothrow|out|override\"\n    keywords=\"${keywords}|package|pragma|private|protected|public|pure|ref|return|scope\"\n    keywords=\"${keywords}|shared|static|struct|super|switch|synchronized|template\"\n    keywords=\"${keywords}|throw|try|typedef|typeid|typeof|union\"\n    keywords=\"${keywords}|unittest|version|volatile|while|with\"\n    attributes=\"abstract|align|auto|const|debug|deprecated|export|extern|final\"\n    attributes=\"${attributes}|immutable|inout|nothrow|package|private|protected\"\n    attributes=\"${attributes}|public|pure|ref|override|scope|shared|static|synchronized|version\"\n    attributes=\"${attributes}|__gshared|__traits|__vector|__parameters\"\n    types=\"bool|byte|cdouble|cent|cfloat|char|creal|dchar|double|dstring|float\"\n    types=\"${types}|idouble|ifloat|int|ireal|long|ptrdiff_t|real|size_t|short\"\n    types=\"${types}|string|ubyte|ucent|uint|ulong|ushort|void|wchar|wstring\"\n    values=\"true|false|null\"\n    tokens=\"__FILE__|__MODULE__|__LINE__|__FUNCTION__\"\n    tokens=\"${tokens}|__PRETTY_FUNCTION__|__DATE__|__EOF__|__TIME__\"\n    tokens=\"${tokens}|__TIMESTAMP__|__VENDOR__|__VERSION__|#line\"\n    properties=\"this|init|sizeof|alignof|mangleof|stringof|infinity|nan|dig|epsilon|mant_dig\"\n    properties=\"${properties}|max_10_exp|min_exp|max|min_normal|re|im|classinfo\"\n    properties=\"${properties}|length|dup|keys|values|rehash|clear\"\n    decorators=\"disable|property|nogc|safe|trusted|system\"\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list d_static_words ${keywords} ${attributes} ${types} ${values} ${decorators} ${properties}\" | tr '|' ' '\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/d/code/ regex \\b(${keywords})\\b 0:keyword\n        add-highlighter shared/d/code/ regex \\b(${attributes})\\b 0:attribute\n        add-highlighter shared/d/code/ regex \\b(${types})\\b 0:type\n        add-highlighter shared/d/code/ regex \\b(${values})\\b 0:value\n        add-highlighter shared/d/code/ regex @(${decorators})\\b 0:attribute\n        add-highlighter shared/d/code/ regex \\b(${tokens})\\b 0:builtin\n        add-highlighter shared/d/code/ regex \\.(${properties})\\b 1:builtin\n    \"\n}\n\nadd-highlighter shared/d/code/ regex \"\\bimport\\s+([\\w._-]+)(?:\\s*=\\s*([\\w._-]+))?\" 1:module 2:module\nadd-highlighter shared/d/code/ regex \"\\bmodule\\s+([\\w_-]+)\\b\" 1:module\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden d-insert-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K/{2,}\\h* <ret> y<c-o>P<esc> }\n    =\n~\n\ndefine-command -hidden d-indent-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # indent after lines ending with { or (\n        try %[ execute-keys -draft kx <a-k> [{(]\\h*$ <ret> j<a-gt> ]\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        # align to opening paren of previous line\n        try %{ execute-keys -draft [( <a-k> \\A\\([^\\n]+\\n[^\\n]*\\n?\\z <ret> s \\A\\(\\h*.|.\\z <ret> '<a-;>' & }\n        # indent after a switch's case/default statements\n        try %[ execute-keys -draft kx <a-k> ^\\h*(case|default).*:$ <ret> j<a-gt> ]\n        # indent after if|else|while|for\n        try %[ execute-keys -draft <semicolon><a-F>)MB <a-k> \\A(if|else|while|for)\\h*\\(.*\\)\\h*\\n\\h*\\n?\\z <ret> s \\A|.\\z <ret> 1<a-&>1<a-,><a-gt> ]\n        # deindent closing brace(s) when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> ]\n    =\n~\n\ndefine-command -hidden d-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden d-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\n§\n"
  },
  {
    "path": "rc/filetype/dart.kak",
    "content": "# https://dartlang.org/\n#\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.dart %{\n    set-option buffer filetype dart\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=dart %{\n    require-module dart\n\n    set-option window static_words %opt{dart_static_words}\n\n    # cleanup trailing whitespaces when exiting insert mode\n    hook window ModeChange pop:insert:.* -group dart-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n    hook window InsertChar \\n -group dart-insert dart-insert-on-new-line\n    hook window InsertChar \\n -group dart-indent dart-indent-on-new-line\n    hook window InsertChar \\{ -group dart-indent dart-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group dart-indent dart-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window dart-.+ }\n}\n\nhook -group dart-highlight global WinSetOption filetype=dart %{\n    add-highlighter window/dart ref dart\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/dart }\n}\n\n\nprovide-module dart %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/dart regions\nadd-highlighter shared/dart/code default-region group\nadd-highlighter shared/dart/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\" group\nadd-highlighter shared/dart/single_string region \"'\" (?<!\\\\)(\\\\\\\\)*' group\nadd-highlighter shared/dart/comment region /\\* \\*/ fill comment\nadd-highlighter shared/dart/comment_line region '//' $ fill comment\n\nadd-highlighter shared/dart/code/ regex %{-?([0-9]*\\.(?!0[xX]))?\\b([0-9]+|0[xX][0-9a-fA-F]+)\\.?([eE][+-]?[0-9]+)?i?\\b} 0:value\n\n# String interpolation\nadd-highlighter shared/dart/double_string/ fill string\nadd-highlighter shared/dart/double_string/ regex \\$\\{.*?\\} 0:value\nadd-highlighter shared/dart/single_string/ fill string\nadd-highlighter shared/dart/single_string/ regex \\$\\{.*?\\} 0:value\n\nevaluate-commands %sh{\n    # Grammar\n    keywords=\"abstract|do|import|super|as|in|switch|assert|else|interface|async\"\n    keywords=\"${keywords}|enum|is|this|export|library|throw|await|external|mixin|break|extends\"\n    keywords=\"${keywords}|new|try|case|factory|typedef|catch|operator|class|final|part|extension\"\n    keywords=\"${keywords}|const|finally|rethrow|while|continue|for|return|with|covariant\"\n    keywords=\"${keywords}|get|set|yield|default|if|static|deferred|implements\"\n    generator_keywords=\"async\\*|sync\\*|yield\\*\"\n\n    types=\"void|bool|num|int|double|dynamic|var\"\n    values=\"false|true|null\"\n\n    annotations=\"@[a-zA-Z]+\"\n    functions=\"(_?[a-z][a-zA-Z0-9]*)(\\(|\\w+=>)\"\n    classes=\"_?[A-Z][a-zA-Z0-9]*\"\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list dart_static_words ${keywords} ${attributes} ${types} ${values}\" | tr '|' ' '\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/dart/code/ regex \\b(${keywords})\\b 0:keyword\n        add-highlighter shared/dart/code/ regex \\b(${generator_keywords}) 0:keyword\n        add-highlighter shared/dart/code/ regex \\b(${attributes})\\b 0:attribute\n        add-highlighter shared/dart/code/ regex \\b(${types})\\b 0:type\n        add-highlighter shared/dart/code/ regex \\b(${values})\\b 0:value\n        add-highlighter shared/dart/code/ regex \\b(${functions}) 2:function\n        add-highlighter shared/dart/code/ regex (${annotations})\\b 0:meta\n        add-highlighter shared/dart/code/ regex \\b(${classes})\\b 0:module\n    \"\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden dart-insert-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K/{2,}\\h* <ret> y<c-o>P<esc> }\n    =\n~\n\ndefine-command -hidden dart-indent-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # indent after lines ending with { or (\n        try %[ execute-keys -draft kx <a-k> [{(]\\h*$ <ret> j<a-gt> ]\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        # align to opening paren of previous line\n        try %{ execute-keys -draft [( <a-k> \\A\\([^\\n]+\\n[^\\n]*\\n?\\z <ret> s \\A\\(\\h*.|.\\z <ret> '<a-;>' & }\n        # indent after a switch's case/default statements\n        try %[ execute-keys -draft kx <a-k> ^\\h*(case|default).*:$ <ret> j<a-gt> ]\n        # indent after if|else|while|for\n        try %[ execute-keys -draft <semicolon><a-F>)MB <a-k> \\A(if|else|while|for)\\h*\\(.*\\)\\h*\\n\\h*\\n?\\z <ret> s \\A|.\\z <ret> 1<a-&>1<a-,><a-gt> ]\n        # deindent closing brace when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> ]\n    =\n~\n\ndefine-command -hidden dart-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden dart-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\n§\n"
  },
  {
    "path": "rc/filetype/dhall.kak",
    "content": "# https://dhall-lang.org\n#                       \n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](dhall) %{\n    set-option buffer filetype dhall\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=dhall %{\n    require-module dhall\n\n    hook window ModeChange pop:insert:.* -group dhall-trim-indent dhall-trim-indent\n    hook window InsertChar \\n -group dhall-insert dhall-insert-on-new-line\n    hook window InsertChar \\n -group dhall-indent dhall-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window dhall-.+ }\n}\n\nhook -group dhall-highlight global WinSetOption filetype=dhall %{\n    add-highlighter window/dhall ref dhall\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/dhall }\n}\n\n\nprovide-module dhall %[\n    \n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/dhall regions\nadd-highlighter shared/dhall/code default-region group\nadd-highlighter shared/dhall/string  region          '\"'          (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/dhall/comment region -recurse \\{- \\{-      -\\}             fill comment\nadd-highlighter shared/dhall/line_comment region -- $ fill comment\n\n# Matches multi-line string literals\nadd-highlighter shared/dhall/multiline_string region \\Q''\\E$ [^']''[^'] fill string\n\n# Matches quoted labels\nadd-highlighter shared/dhall/quoted_label region ` ` fill normal\n\n# Matches built-in types\nadd-highlighter shared/dhall/code/ regex \\b(Location|Sort|Kind|Type|Text|Bool|Natural|Integer|Double|List|Optional|\\{\\})\\b 0:type\n\n# Matches built-in keywords\nadd-highlighter shared/dhall/code/ regex \\b(if|then|else|let|in|using|missing|as|merge|toMap)\\b 0:keyword\n\n# Matches bulit-in values\nadd-highlighter shared/dhall/code/ regex \\b(True|False|Some|None|-?Infinity|\\{=\\}|NaN)\\b 0:value\n\n# Matches built-in operators\nadd-highlighter shared/dhall/code/ regex (,|:|\\|\\||&&|==|!=|=|\\+|\\*|\\+\\+|#|⩓|//\\\\\\\\|→|->|\\?|λ|\\\\|\\^|⫽|//|\\[|\\]|\\{|\\}) 0:operator\n\n# Matches built-in functions\nadd-highlighter shared/dhall/code/ regex \\b(Natural-fold|Natural-build|Natural-isZero|Natural-even|Natural-odd|Natural-toInteger|Natural-show|Integer-toDouble|Integer-show|Natural-subtract|Double-show|List-build|List-fold|List-length|List-head|List-last|List-indexed|List-reverse|Optional-fold|Optional-build|Text-show)\\b 0:keyword\n\n# Matches http[s] imports\nadd-highlighter shared/dhall/code/ regex \\b(http[s]://\\S+)\\b(\\s+sha256:[a-f0-9]{64}\\b)? 0:meta\n\n# Matches local imports\nadd-highlighter shared/dhall/code/ regex (~|\\.|\\.\\.|/)\\S+ 0:meta\n\n# Matches number (natural, integer, double) literals\nadd-highlighter shared/dhall/code/ regex \\b(\\+|-)?\\d+(\\.\\d+)?(e(\\+|-)?\\d+)?\\b 0:value\n\n# Matches union syntax\nadd-highlighter shared/dhall/union region -recurse < < > group\nadd-highlighter shared/dhall/union/sep regex (<|\\|)\\s*((?:_|[A-Z])(?:[a-zA-Z0-9-/_]*))\\s*(?:(:)([^|>]*))? 1:operator 2:attribute 3:operator 4:type\nadd-highlighter shared/dhall/union/end regex > 0:operator\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden dhall-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden dhall-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy -- comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K--\\h* <ret> y gh j P }\n    }\n}\ndefine-command -hidden dhall-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft \\; K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : dhall-trim-indent <ret> }\n        # indent after lines ending with let, : or =\n        try %{ execute-keys -draft \\; k x <a-k> (\\blet|:|=)$ <ret> j <a-gt> }\n    }\n}\n\n]\n"
  },
  {
    "path": "rc/filetype/diff-parse.pl",
    "content": "#!/usr/bin/env perl\n\nuse warnings;\n\nsub quote {\n    my $token = shift;\n    $token =~ s/'/''/g;\n    return \"'$token'\";\n}\nsub fail {\n    my $reason = shift;\n    print quote(\"diff-parse.pl: $reason\");\n    exit 1;\n}\n\nmy $begin;\nmy $end;\n\nwhile (defined $ARGV[0]) {\n    if ($ARGV[0] eq \"--\") {\n        shift;\n        last;\n    }\n    if ($ARGV[0] =~ m{^(BEGIN|END)$}) {\n        if (not defined $ARGV[1]) {\n            fail \"missing argument to $ARGV[0]\";\n        }\n        if ($ARGV[0] eq \"BEGIN\") {\n            $begin = $ARGV[1];\n        } else {\n            $end = $ARGV[1];\n        }\n        shift, shift;\n        next;\n    }\n    fail \"unknown argument: $ARGV[0]\";\n}\n\n# Inputs\nour $directory = $ENV{PWD};\nour $strip;\nour $in_file;\nour $in_file_line;\nour $version = \"+\";\n\neval $begin if defined $begin;\n\n$in_file = \"$directory/$in_file\" if defined $in_file && $in_file ne \"\";\n\n# Outputs\nour $diff_line = 0;\nour $commit;\nour $file;\nour $file_line;\nour $other_file;\nour $other_file_line;\nour $diff_line_text;\n\nmy $other_version;\nif ($version eq \"+\") {\n    $other_version = \"-\";\n} else {\n    $other_version = \"+\";\n}\nmy $is_recursive_diff = 0;\nmy $state = \"header\";\nmy $fallback_file;\n\nsub strip {\n    my $is_recursive_diff = shift;\n    my $f = shift;\n\n    my $effective_strip;\n    if (defined $strip) {\n        $effective_strip = $strip;\n    } else {\n        # A \"diff -r\" or \"git diff\" adds \"diff\" lines to\n        # the output.  If no such line is present, we have\n        # a plain diff between files (not directories), so\n        # there should be no need to strip the directory.\n        $effective_strip = $is_recursive_diff ? 1 : 0;\n    }\n\n    if ($f !~ m{^/}) {\n        $f =~ s,^([^/]+/+){$effective_strip},, or fail \"directory prefix underflow\";\n        $f = \"$directory/$f\";\n    }\n    return $f;\n}\n\nwhile (<STDIN>) {\n    $diff_line++;\n    s/^(> )*//g;\n    $diff_line_text = $_;\n    if (m{^commit (\\w+)}) {\n        $commit = $1;\n        next;\n    }\n    if (m{^diff\\b}) {\n        $state = \"header\";\n        $is_recursive_diff = 1;\n        if (m{^diff -\\S* (\\S+) (\\S+)$}) {\n            $fallback_file = strip $is_recursive_diff, ($version eq \"+\" ? $2 : $1);\n        }\n        next;\n    }\n    if ($state eq \"header\") {\n        if (m{^[$version]{3} ([^\\t\\n]+)}) {\n            $file = strip $is_recursive_diff, $1;\n            next;\n        }\n        if (m{^[$other_version]{3} ([^\\t\\n]+)}) {\n            $other_file = strip $is_recursive_diff, $1;\n            next;\n        }\n    }\n    if (m{^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@}) {\n        $state = \"contents\";\n        $file_line = ($version eq \"+\" ? $2 : $1) - 1;\n        $other_file_line = ($version eq \"+\" ? $1 : $2) - 1;\n    } else {\n        my $iscontext = m{^[ ]};\n        if (m{^[ $version]}) {\n           $file_line++ if defined $file_line;\n        }\n        if (m{^[ $other_version]}) {\n           $other_file_line++ if defined $other_file_line;\n        }\n    }\n    if (defined $in_file and defined $file and ($in_file eq \"\" or $file eq $in_file)) {\n        if (defined $in_file_line and defined $file_line and $file_line >= $in_file_line) {\n            last;\n        }\n    }\n}\nif (not defined $file) {\n    $file = ($fallback_file or $other_file);\n}\nif (not defined $file) {\n    fail \"missing diff header\";\n}\n\neval $end if defined $end;\n"
  },
  {
    "path": "rc/filetype/diff.kak",
    "content": "hook global BufCreate .*\\.(diff|patch) %{\n    set-option buffer filetype diff\n}\n\nhook global WinSetOption filetype=diff %{\n    require-module diff\n    map buffer normal <ret> :diff-jump<ret>\n}\n\nhook -group diff-highlight global WinSetOption filetype=diff %{\n    add-highlighter window/diff ref diff\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/diff }\n}\n\nprovide-module diff %§\n\nadd-highlighter shared/diff group\nadd-highlighter shared/diff/ regex \"^\\+[^\\n]*\\n\" 0:green,default\nadd-highlighter shared/diff/ regex \"^-[^\\n]*\\n\" 0:red,default\nadd-highlighter shared/diff/ regex \"^@@[^\\n]*@@\" 0:cyan,default\n# If any trailing whitespace was introduced in diff, show it with red background\nadd-highlighter shared/diff/ regex \"^\\+[^\\n]*?(\\h+)\\n\" 1:default,red\n\ndefine-command diff-jump -params .. -docstring %{\n        diff-jump [<switches>] [<directory>]: edit the diff's source file at the cursor position.\n        Paths are resolved relative to <directory>, or the current working directory if unspecified.\n\n        Switches:\n            -       jump to the old file instead of the new file\n            -<num> strip <num> leading directory components, like -p<num> in patch(1). Defaults to 1 if there is a 'diff' line (as printed by 'diff -r'), or 0 otherwise.\n    } %{\n    evaluate-commands -draft -save-regs c %{\n        # Save the column because we will move the cursor.\n        set-register c %val{cursor_column}\n        # If there is a \"diff\" line, we don't need to look further back.\n        try %{\n            execute-keys %{,<a-l><semicolon><a-?>^(?:> )*diff\\b<ret>x}\n        } catch %{\n            # A single file diff won't have a diff line. Start parsing from\n            # the buffer start, so we can tell if +++/--- lines are headers\n            # or content.\n            execute-keys Gk\n        }\n        diff-parse BEGIN %{\n            my $seen_ddash = 0;\n            foreach (@ARGV) {\n                if ($seen_ddash or !m{^-}) {\n                    $directory = $_;\n                } elsif ($_ eq \"-\") {\n                    $version = \"-\", $other_version = \"+\";\n                } elsif (m{^-(\\d+)$}) {\n                    $strip = $1;\n                } elsif ($_ eq \"--\") {\n                    $seen_ddash = 1;\n                } else {\n                    fail \"unknown option: $_\";\n                }\n            }\n        } END %exp{\n            my $file_column;\n            if (not defined $file_line) {\n                $file_line = \"\";\n                $file_column = \"\";\n            } else {\n                my $diff_column = %reg{c};\n                $file_column = $diff_column - 1; # Account for [ +-] diff prefix.\n                # If the cursor was on a hunk header, go to the section header if possible.\n                if ($diff_line_text =~ m{^(@@ -\\d+(?:,\\d+)? \\+\\d+(?:,\\d+) @@ )([^\\n]*)}) {\n                    my $hunk_header_prefix = $1;\n                    my $hunk_header_from_userdiff = $2;\n                    open FILE, \"<\", $file or fail \"failed to open file: $!: $file\";\n                    my @lines = <FILE>;\n                    for (my $i = $file_line - 1; $i >= 0 and $i < scalar @lines; $i--) {\n                        if ($lines[$i] !~ m{\\Q$hunk_header_from_userdiff}) {\n                            next;\n                        }\n                        $file_line = $i + 1;\n                        # Re-add 1 because the @@ line does not have a [ +-] diff prefix.\n                        $file_column = $diff_column + 1 - length $hunk_header_prefix;\n                        last;\n                    }\n                }\n            }\n            printf \"set-register c %%s $file_line $file_column\\n\", quote($file);\n        } -- %arg{@}\n        evaluate-commands -client %val{client} %{\n            evaluate-commands -try-client %opt{jumpclient} %{\n                edit -existing -- %reg{c}\n            }\n        }\n    }\n}\ncomplete-command diff-jump file\n\ndefine-command -hidden diff-parse -params 2.. %{\n    evaluate-commands -save-regs ae| %{\n        set-register a %arg{@}\n        set-register e nop\n        set-register | %{\n            eval set -- \"$kak_quoted_reg_a\"\n            if ! result=$(perl \"${kak_runtime}/rc/filetype/diff-parse.pl\" \"$@\"); then\n                printf 'set-register e %s\\n' \"fail $result\"\n            else\n                printf '%s\\n' \"$result\"\n            fi >\"$kak_command_fifo\"\n        }\n        execute-keys <a-|><ret>\n        %reg{e}\n    }\n}\n\n§\n\ndefine-command \\\n    -docstring %{diff-select-file: Select surrounding patch file} \\\n    -params 0 \\\n    diff-select-file %{\n                evaluate-commands -itersel -save-regs 'ose/' %{\n        try %{\n            execute-keys '\"oZgl<a-?>^diff <ret>;\"sZ' 'Ge\"eZ'\n            try %{ execute-keys '\"sz?\\n(?=diff )<ret>\"e<a-Z><lt>' }\n            execute-keys '\"ez'\n        } catch %{\n            execute-keys '\"oz'\n            fail 'Not in a diff file'\n        }\n    }\n}\n\ndefine-command \\\n    -docstring %{diff-select-hunk: Select surrounding patch hunk} \\\n    -params 0 \\\n    diff-select-hunk %{\n    evaluate-commands -itersel -save-regs 'ose/' %{\n        try %{\n            execute-keys '\"oZgl<a-?>^@@ <ret>;\"sZ' 'Ge\"eZ'\n            try %{ execute-keys '\"sz?\\n(?=diff )<ret>\"e<a-Z><lt>' }\n            try %{ execute-keys '\"sz?\\n(?=@@ )<ret>\"e<a-Z><lt>' }\n            execute-keys '\"ez'\n        } catch %{\n            execute-keys '\"oz'\n            fail 'Not in a diff hunk'\n        }\n    }\n}\n"
  },
  {
    "path": "rc/filetype/dockerfile.kak",
    "content": "# http://docker.com\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# See https://docs.docker.com/reference/builder\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*/?Dockerfile(\\..+)?$ %{\n    set-option buffer filetype dockerfile\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=dockerfile %{\n    require-module dockerfile\n    set-option window static_words %opt{dockerfile_static_words}\n}\n\nhook -group dockerfile-highlight global WinSetOption filetype=dockerfile %{\n    add-highlighter window/dockerfile ref dockerfile\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/dockerfile }\n}\n\nprovide-module dockerfile %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/dockerfile regions\nadd-highlighter shared/dockerfile/code default-region group\nadd-highlighter shared/dockerfile/double_string region '\"' '(?<!\\\\)(\\\\\\\\)*\"' fill string\nadd-highlighter shared/dockerfile/single_string region \"'\" \"'\"               fill string\nadd-highlighter shared/dockerfile/comment region '#' $ fill comment\n\nevaluate-commands %sh{\n    # Grammar\n    keywords=\"ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL\"\n    keywords=\"${keywords}|MAINTAINER|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR\"\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list dockerfile_static_words ONBUILD|${keywords}\" | tr '|' ' '\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/dockerfile/code/ regex '^(?i)(ONBUILD\\h+)?(${keywords})\\b' 2:keyword\n        add-highlighter shared/dockerfile/code/ regex '^(?i)(ONBUILD)\\h+' 1:keyword\n    \"\n}\n\nadd-highlighter shared/dockerfile/code/ regex (?<!\\\\)(?:\\\\\\\\)*\\K\\$\\{[\\w_]+\\} 0:value\nadd-highlighter shared/dockerfile/code/ regex (?<!\\\\)(?:\\\\\\\\)*\\K\\$[\\w_]+ 0:value\n\n}\n"
  },
  {
    "path": "rc/filetype/elixir.kak",
    "content": "# http://elixir-lang.org\n# ----------------------\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](ex|exs) %{\n    set-option buffer filetype elixir\n}\n\nhook global BufCreate .*[.]html[.][lh]?eex %{\n    set-option buffer filetype eex\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=elixir %{\n    require-module elixir\n\n    hook window ModeChange pop:insert:.* -group elixir-trim-indent elixir-trim-indent\n    hook window InsertChar \\n -group elixir-indent elixir-indent-on-new-line\n    hook window InsertChar \\n -group elixir-insert elixir-insert-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window elixir-.+ }\n}\n\nhook -group elixir-highlight global WinSetOption filetype=elixir %{\n    add-highlighter window/elixir ref elixir\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/elixir }\n}\n\nhook global WinSetOption filetype=eex %{\n    require-module eex\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window eex-.+ }\n}\n\nhook -group eex-highlight global WinSetOption filetype=eex %{\n    add-highlighter window/eex ref eex\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/eex }\n}\n\nprovide-module eex %{\nrequire-module html\nrequire-module elixir\n\nadd-highlighter shared/eex regions\nadd-highlighter shared/eex/html default-region ref html\nadd-highlighter shared/eex/comment region '<%#' '%>' fill comment\nadd-highlighter shared/eex/quote region '<%%' '%>' ref html\nadd-highlighter shared/eex/code region '<%=?' '%>' ref elixir\n}\n\nprovide-module elixir %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/elixir regions\nadd-highlighter shared/elixir/code default-region group\nadd-highlighter shared/elixir/double_string region -match-capture (\"\"\"|\") (?<!\\\\)(?:\\\\\\\\)*(\"\"\"|\") regions\nadd-highlighter shared/elixir/single_string region \"'\" \"(?<!\\\\)(?:\\\\\\\\)*'\" fill string\nadd-highlighter shared/elixir/comment region '#' '$' fill comment\n\nadd-highlighter shared/elixir/leex region -match-capture '~L(\"\"\"|\")' '(?<!\\\\)(?:\\\\\\\\)*(\"\"\"|\")' ref eex\nadd-highlighter shared/elixir/heex region -match-capture '~H(\"\"\"|\")' '(?<!\\\\)(?:\\\\\\\\)*(\"\"\"|\")' ref eex\n\nadd-highlighter shared/elixir/double_string/base default-region fill string\nadd-highlighter shared/elixir/double_string/interpolation region -recurse \\{ \\Q#{ \\} fill builtin\n\nadd-highlighter shared/elixir/code/ regex ':[\\w_]+\\b' 0:type\nadd-highlighter shared/elixir/code/ regex '[\\w_]+:' 0:type\nadd-highlighter shared/elixir/code/ regex '[A-Z][\\w_]+\\b' 0:module\nadd-highlighter shared/elixir/code/ regex '(:[\\w_]+)(\\.)' 1:module\nadd-highlighter shared/elixir/code/ regex '\\b_\\b' 0:default\nadd-highlighter shared/elixir/code/ regex '\\b_[\\w_]+\\b' 0:default\nadd-highlighter shared/elixir/code/ regex '~[a-zA-Z]\\(.*?[^\\\\]\\)' 0:string\nadd-highlighter shared/elixir/code/ regex \\b(true|false|nil)\\b 0:value\nadd-highlighter shared/elixir/code/ regex (->|<-|<<|>>|=>) 0:builtin\nadd-highlighter shared/elixir/code/ regex \\b(require|alias|use|import)\\b 0:keyword\nadd-highlighter shared/elixir/code/ regex \\b(__MODULE__|__DIR__|__ENV__|__CALLER__)\\b 0:value\nadd-highlighter shared/elixir/code/ regex \\b(def|defp|defmacro|defmacrop|defstruct|defmodule|defimpl|defprotocol|defoverridable)\\b 0:keyword\nadd-highlighter shared/elixir/code/ regex \\b(fn|do|end|when|case|if|else|unless|var!|for|cond|quote|unquote|receive|with|raise|reraise|try|catch)\\b 0:keyword\nadd-highlighter shared/elixir/code/ regex '@[\\w_]+\\b' 0:attribute\nadd-highlighter shared/elixir/code/ regex '\\b\\d+[\\d_]*\\b' 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden elixir-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden elixir-insert-on-new-line %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # copy '#' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y jgi P }\n        # wisely add end structure\n        evaluate-commands -save-regs x %[\n            try %{ execute-keys -draft k x s ^ \\h + <ret> \\\" x y } catch %{ reg x '' }\n            try %[\n                evaluate-commands -draft %[\n                    # Check if previous line opens a block\n                    execute-keys -draft kx <a-k>^<c-r>x(.+\\bdo$)<ret>\n                    # Check that we do not already have an end for this indent level which is first set via `elixir-indent-on-new-line` hook\n                    execute-keys -draft }i J x <a-K> ^<c-r>x(end|else)[^0-9A-Za-z_!?]<ret>\n                ]\n                execute-keys -draft o<c-r>xend<esc> # insert a new line with containing end\n            ]\n        ]\n    ]\n]\n\ndefine-command -hidden elixir-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # indent after line ending with:\n        # try %{ execute-keys -draft k x <a-k> (\\bdo|\\belse|->)$ <ret> & }\n        # filter previous line\n        try %{ execute-keys -draft k : elixir-trim-indent <ret> }\n        # indent after lines ending with do or ->\n        try %{ execute-keys -draft <semicolon> k x <a-k> ^.+(\\bdo|->)$ <ret> j <a-gt> }\n    }\n}\n\n]\n"
  },
  {
    "path": "rc/filetype/elm.kak",
    "content": "# http://elm-lang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](elm) %{\n    set-option buffer filetype elm\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=elm %{\n    require-module elm\n\n    hook window ModeChange pop:insert:.* -group elm-trim-indent elm-trim-indent\n    hook window InsertChar \\n -group elm-insert elm-insert-on-new-line\n    hook window InsertChar \\n -group elm-indent elm-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window elm-.+ }\n}\n\nhook -group elm-highlight global WinSetOption filetype=elm %{\n    add-highlighter window/elm ref elm\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/elm }\n}\n\n\nprovide-module elm %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/elm               regions\nadd-highlighter shared/elm/code          default-region group\nadd-highlighter shared/elm/multiline_string region '\"\"\"' '\"\"\"' fill string\nadd-highlighter shared/elm/string        region         '\"'     (?<!\\\\)(\\\\\\\\)*\"       fill string\nadd-highlighter shared/elm/line_comment  region         (--) $                        fill comment\nadd-highlighter shared/elm/comment       region         -recurse \\{- \\{-   -\\}        fill comment\n\nadd-highlighter shared/elm/code/ regex \\b[A-Z]\\w*\\b                                                        0:type\nadd-highlighter shared/elm/code/ regex \\b[a-z]\\w*\\b                                                        0:variable\nadd-highlighter shared/elm/code/ regex ^[a-z]\\w*\\b                                                         0:function\nadd-highlighter shared/elm/code/ regex \"-?\\b[0-9]*\\.?[0-9]+\"                                               0:value\nadd-highlighter shared/elm/code/ regex \\B[-+<>!@#$%^&*=:/\\\\|]+\\B                                           0:operator\nadd-highlighter shared/elm/code/ regex \\b(import|exposing|as|module|port)\\b                                0:meta\nadd-highlighter shared/elm/code/ regex \\b(type|alias|if|then|else|case|of|let|in|infix|_)\\b)               0:keyword\nadd-highlighter shared/elm/code/ regex (?<![-+<>!@#$%^&*=:/\\\\|])(->|:|=|\\|)(?![-+<>!@#$%^&*=:/\\\\|])        0:keyword\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\n# http://elm-lang.org/docs/style-guide\n\ndefine-command -hidden elm-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden elm-indent-after \"\n execute-keys -draft <semicolon> k x <a-k> ^\\\\h*if|[=(]$|\\\\b(case\\\\h+[\\\\w']+\\\\h+of|let|in)$|(\\\\{\\\\h+\\\\w+|\\\\w+\\\\h+->)$ <ret> j <a-gt>\n\"\n\ndefine-command -hidden elm-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy -- comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K--\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden elm-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # align to first clause\n        try %{ execute-keys -draft <semicolon> k x X s ^\\h*(if|then|else)?\\h*(([\\w']+\\h+)+=)?\\h*(case\\h+[\\w']+\\h+of|let)\\h+\\K.* <ret> s \\A|.\\z <ret> & }\n        # filter previous line\n        try %{ execute-keys -draft k : elm-trim-indent <ret> }\n        # indent after lines beginning with condition or ending with expression or =(\n        try %{ elm-indent-after }\n    }\n}\n\n]\n"
  },
  {
    "path": "rc/filetype/elvish.kak",
    "content": "# Syntax highlighting and indentation for Elvish (https://elv.sh)\n\nhook global BufCreate .*\\.elv %{\n    set-option buffer filetype elvish\n}\n\nhook global WinSetOption filetype=elvish %<\n    require-module elvish\n\n    hook window ModeChange pop:insert:.* -group elvish-trim-indent elvish-trim-indent\n    hook window InsertChar \\n -group elvish-insert elvish-indent\n    hook window InsertChar [\\]})] -group elvish-insert elvish-deindent\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window elvish-.+ }\n>\n\nhook -group elvish-highlight global WinSetOption filetype=elvish %{\n    add-highlighter window/elvish ref elvish\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/elvish }\n}\n\nprovide-module elvish %§\n\nadd-highlighter shared/elvish regions\nadd-highlighter shared/elvish/code default-region group\nadd-highlighter shared/elvish/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/elvish/single_string region \"'\" ('')*' fill string\nadd-highlighter shared/elvish/comment region '#' $ fill comment\n\nadd-highlighter shared/elvish/code/variable regex \\$[\\w\\d-_:~]+ 0:variable\nadd-highlighter shared/elvish/code/variable_in_assignment regex (?:^|\\{\\s|\\(|\\||\\;)\\h*(?:var|set|tmp|del)((?:\\h+[\\w\\d-_:~]+)*) 1:variable\nadd-highlighter shared/elvish/code/variable_in_for regex (?:^|\\{\\s|\\(|\\||\\;)\\h*for\\h+([\\w\\d-_:~]*) 1:variable\nadd-highlighter shared/elvish/code/variable_in_catch regex \\}\\h+(?:catch|except)\\h+([\\w\\d-_:~]*) 1:variable\n\nadd-highlighter shared/elvish/code/builtin regex (?:^|\\{\\s|\\(|\\||\\;)\\h*(!=|!=s|%|\\*|\\+|-gc|-ifaddrs|-log|-override-wcwidth|-stack|-|/|<|<=|<=s|<s|==|==s|>|>=|>=s|>s|all|assoc|base|bool|break|call|cd|compare|constantly|continue|count|defer|deprecate|dissoc|drop|each|eawk|echo|eq|eval|exact-num|exec|exit|external|fail|fg|float64|from-json|from-lines|from-terminated|get-env|has-env|has-external|has-key|has-value|is|keys|kind-of|make-map|multi-error|nop|not-eq|not|ns|num|one|only-bytes|only-values|order|peach|pprint|print|printf|put|rand|randint|range|read-line|read-upto|repeat|repr|resolve|return|run-parallel|search-external|set-env|show|sleep|slurp|src|styled|styled-segment|take|tilde-abbr|time|to-json|to-lines|to-string|to-terminated|unset-env|use-mod|wcswidth)(?![-:])\\b 1:builtin\nadd-highlighter shared/elvish/code/keyword regex (?:^|\\{\\s|\\(|\\||\\;)\\h*(use|var|set|tmp|del|and|or|coalesce|pragma|while|for|try|fn|if)(?![-:])\\b 1:keyword\nadd-highlighter shared/elvish/code/keyword_block regex \\}\\h+(catch|elif|else|except|finally)(?![-:])\\b 1:keyword\n\nadd-highlighter shared/elvish/code/metachar regex [*?|&\\;<>()[\\]{}] 0:operator\n\ndefine-command -hidden elvish-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden elvish-indent %< evaluate-commands -draft -itersel %<\n    execute-keys <semicolon>\n    try %<\n        # if the previous line is a comment, copy indent, # and whitespace\n        execute-keys -draft k x s^\\h*#\\h*<ret> yjP\n    > catch %<\n        # copy indent\n        execute-keys -draft K <a-&>\n        # indent after { [ ( |\n        try %< execute-keys -draft k x <a-k>[[{(|]\\h*$<ret> j <a-gt> >\n    >\n>>\n\ndefine-command -hidden elvish-deindent %< evaluate-commands -draft -itersel %<\n    try %<\n        # Deindent only when there is a lone closing character\n        execute-keys -draft x <a-k>^\\h*[^\\h]$<ret> <a-lt>\n    >\n>>\n§\n"
  },
  {
    "path": "rc/filetype/erlang.kak",
    "content": "# Erlang/OTP\n# https://erlang.org\n# ----------------------\n\n# Detection and Initialization sections were adapted from rc/filetype/elixir.kak\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\nhook global BufCreate .*[.](erl|hrl) %{\n    set-option buffer filetype erlang\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\nhook global WinSetOption filetype=erlang %{\n    require-module erlang\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window erlang-.+ }\n}\n\nhook -group erlang-highlight global WinSetOption filetype=erlang %{\n    add-highlighter window/erlang ref erlang\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/erlang }\n}\n\nprovide-module erlang %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/erlang regions\nadd-highlighter shared/erlang/default default-region group\n\nadd-highlighter shared/erlang/comment region '(?<!\\$)%' '$' fill comment\nadd-highlighter shared/erlang/attribute_atom_single_quoted region %{-'} %{(?<!\\\\)(?:\\\\\\\\)*'(?=[\\( \\.])} fill builtin\nadd-highlighter shared/erlang/attribute region '\\b-[a-z][\\w@]*(?=[\\( \\.])' '\\K' fill builtin\nadd-highlighter shared/erlang/atom_single_quoted region %{'} %{(?<!\\\\)(?:\\\\\\\\)*'} fill type\nadd-highlighter shared/erlang/char_list region %{\"} %{(?<!\\\\)(?:\\\\\\\\)*\"} fill string\nadd-highlighter shared/erlang/dollar_double_quote region %{\\$\\\\?\"} '\\K' fill value\nadd-highlighter shared/erlang/dollar_single_quote region %{\\$\\\\?'} '\\K' fill value\n\n# default-region regex highlighters\nadd-highlighter shared/erlang/default/atom regex '\\b[a-z][\\w@]*\\b' 0:type\nadd-highlighter shared/erlang/default/funtion_call regex '\\b[a-z][\\w@]*(?=\\()' 0:function\nadd-highlighter shared/erlang/default/keywords regex '\\b(after|begin|case|try|catch|end|fun|if|of|receive|when|andalso|orelse|bnot|not|div|rem|band|and|bor|bxor|bsl|bsr|or|xor)\\b' 0:keyword\nadd-highlighter shared/erlang/default/variable_name regex '\\b(?<!\\?)[A-Z_][\\w@]*\\b' 0:variable\nadd-highlighter shared/erlang/default/module_prefix regex '\\b([a-z][\\w_@]+)(?=:)' 1:value\n# e.g. #Ref<0.1380825548.292038421.197518>\nadd-highlighter shared/erlang/default/ref regex '#Ref<\\d+\\.\\d+\\.\\d+\\.\\d+>' 0:value\n# e.g. #Port<0.1>\nadd-highlighter shared/erlang/default/port regex '#Port<\\d+\\.\\d+>' 0:value\n# e.g. <0.401.0>\nadd-highlighter shared/erlang/default/pid regex '<\\d+\\.\\d+\\.\\d+>' 0:value\nadd-highlighter shared/erlang/default/base_number regex '\\b(\\d[_\\d]*(?<!_)#[a-zA-Z0-9][a-z_A-Z0-9]*(?<!_)(?!\\{))\\b' 1:value\nadd-highlighter shared/erlang/default/float regex '\\b(?<![\\.])(\\d[\\d_]*(?<!_)\\.\\d[\\d_]*(?<!_)(?:e[+-]?\\d[\\d_]*(?<!_))?)\\b' 1:value\nadd-highlighter shared/erlang/default/integer regex '\\b(?<!/)(\\d[\\d_]*)(?<!_)\\b' 1:value\n# e.g $\\xff\nadd-highlighter shared/erlang/default/dollar_hex regex '\\$\\\\x[0-9a-f][0-9a-f]' 0:value\n# e.g. $\\^a $\\^C\nadd-highlighter shared/erlang/default/dollar_ctrl_char regex '\\$\\\\\\^[a-zA-Z]' 0:value\n# e.g. $\\101, $\\70\nadd-highlighter shared/erlang/default/dollar_octal regex '\\$\\\\[0-7][0-7][0-7]?' 0:value\n# e.g. $\\↕ $\\7\nadd-highlighter shared/erlang/default/dollar_esc_char regex '\\$\\\\[^x]' 0:value\n# e.g. $↕ $a\nadd-highlighter shared/erlang/default/dollar_char regex '\\$.' 0:value\n\n]\n"
  },
  {
    "path": "rc/filetype/eruby.kak",
    "content": "# eRuby\n# http://www2a.biglobe.ne.jp/~seki/ruby/erb.html\n\nhook global BufCreate '.*\\.erb' %{\n  set-option buffer filetype eruby\n}\n\nhook global WinSetOption filetype=eruby %{\n  require-module eruby\n  add-highlighter window/eruby ref eruby\n  hook -group eruby-indent window InsertChar '\\n' html-indent-on-new-line\n  hook -always -once window WinSetOption filetype=.* %{\n    remove-highlighter window/eruby\n    remove-hooks window eruby-.+\n  }\n}\n\nprovide-module eruby %{\n  require-module ruby\n  require-module html\n  add-highlighter shared/eruby regions\n  add-highlighter shared/eruby/html default-region ref html\n  add-highlighter shared/eruby/simple-comment-tag region '<%#' '%>' fill comment\n  add-highlighter shared/eruby/simple-execution-tag region (?<=<%) '%>' ref ruby\n}\n"
  },
  {
    "path": "rc/filetype/etc.kak",
    "content": "# Highlighting for common files in /etc\nhook global BufCreate .*/etc/(hosts|networks|services)  %{ set-option buffer filetype etc-hosts }\nhook global BufCreate .*/etc/resolv.conf                %{ set-option buffer filetype etc-resolv-conf }\nhook global BufCreate .*/etc/shadow                     %{ set-option buffer filetype etc-shadow }\nhook global BufCreate .*/etc/passwd                     %{ set-option buffer filetype etc-passwd }\nhook global BufCreate .*/etc/gshadow                    %{ set-option buffer filetype etc-gshadow }\nhook global BufCreate .*/etc/group                      %{ set-option buffer filetype etc-group }\nhook global BufCreate .*/etc/(fs|m)tab                  %{ set-option buffer filetype etc-fstab }\nhook global BufCreate .*/etc/environment                %{ set-option buffer filetype sh }\nhook global BufCreate .*/etc/env.d/.*                   %{ set-option buffer filetype sh }\nhook global BufCreate .*/etc/profile(\\.(csh|env))?      %{ set-option buffer filetype sh }\nhook global BufCreate .*/etc/profile\\.d/.*              %{ set-option buffer filetype sh }\n\n\nhook global WinSetOption filetype=etc-(hosts|resolv-conf|shadow|passwd|gshadow|group|fstab) %{\n    require-module \"etc-%val{hook_param_capture_1}\"\n}\n\n\nhook -group etc-resolv-conf-highlight global WinSetOption filetype=etc-resolv-conf %{\n    add-highlighter window/etc-resolv-conf ref etc-resolv-conf\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/etc-resolv-conf }\n}\nhook -group etc-hosts-highlight global WinSetOption filetype=etc-hosts %{\n    add-highlighter window/etc-hosts ref etc-hosts\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/etc-hosts }\n}\nhook -group etc-fstab-highlight global WinSetOption filetype=etc-fstab %{\n    add-highlighter window/etc-fstab ref etc-fstab\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/etc-fstab }\n}\nhook -group etc-group-highlight global WinSetOption filetype=etc-group %{\n    add-highlighter window/etc-group ref etc-group\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/etc-group }\n}\nhook -group etc-gshadow-highlight global WinSetOption filetype=etc-gshadow %{\n    add-highlighter window/etc-gshadow ref etc-gshadow\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/etc-gshadow }\n}\nhook -group etc-shadow-highlight global WinSetOption filetype=etc-shadow %{\n    add-highlighter window/etc-shadow ref etc-shadow\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/etc-shadow }\n}\nhook -group etc-passwd-highlight global WinSetOption filetype=etc-passwd %{\n    add-highlighter window/etc-passwd ref etc-passwd\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/etc-passwd }\n}\n\n\n# Highlighters\n\nprovide-module etc-resolv-conf %{\n## /etc/resolv.conf\nadd-highlighter shared/etc-resolv-conf group\nadd-highlighter shared/etc-resolv-conf/ regex ^#.*?$ 0:comment\nadd-highlighter shared/etc-resolv-conf/ regex ^(nameserver|server|domain|sortlist|options)[\\s\\t]+(.*?)$ 1:type 2:attribute\n}\n\nprovide-module etc-hosts %{\n## /etc/hosts\nadd-highlighter shared/etc-hosts group\nadd-highlighter shared/etc-hosts/ regex ^(.+?)[\\s\\t]+?(.*?)$ 1:type 2:attribute\nadd-highlighter shared/etc-hosts/ regex '#.*?$' 0:comment\n}\n\nprovide-module etc-fstab %{\n## /etc/fstab\nadd-highlighter shared/etc-fstab group\nadd-highlighter shared/etc-fstab/ regex ^(\\S{1,})\\s+?(\\S{1,})\\s+?(\\S{1,})\\s+?(\\S{1,})\\s+?(\\S{1,})\\s+?(\\S{1,})(?:\\s+)?$ 1:keyword 2:value 3:type 4:string 5:attribute 6:attribute\nadd-highlighter shared/etc-fstab/ regex '#.*?$' 0:comment\n}\n\nprovide-module etc-group %{\n## /etc/group\nadd-highlighter shared/etc-group group\nadd-highlighter shared/etc-group/ regex ^(\\S+?):(\\S+?)?:(\\S+?)?:(\\S+?)?$ 1:keyword 2:type 3:value 4:string\n}\n\nprovide-module etc-gshadow %{\n## /etc/gshadow\nadd-highlighter shared/etc-gshadow group\nadd-highlighter shared/etc-gshadow/ regex ^(\\S+?):(\\S+?)?:(\\S+?)?:(\\S+?)?$ 1:keyword 2:type 3:value 4:string\n}\n\nprovide-module etc-shadow %{\n## /etc/shadow\nadd-highlighter shared/etc-shadow group\nadd-highlighter shared/etc-shadow/ regex ^(\\S+?):(\\S+?):([0-9]+?):([0-9]+?)?:([0-9]+?)?:([0-9]+?)?:([0-9]+?)?:([0-9]+?)?:(.*?)?$ 1:keyword 2:type 3:value 4:value 5:value 6:value 7:value 8:value\n}\n\nprovide-module etc-passwd %{\n## /etc/passwd\nadd-highlighter shared/etc-passwd group\nadd-highlighter shared/etc-passwd/ regex ^(\\S+?):(\\S+?):([0-9]+?):([0-9]+?):(.*?)?:(.+?):(.+?)$ 1:keyword 2:type 3:value 4:value 5:string 6:attribute 7:attribute\n}\n"
  },
  {
    "path": "rc/filetype/exherbo.kak",
    "content": "## Repository metadata files\nhook global BufCreate .*/metadata/mirrors\\.conf         %{ set-option buffer filetype paludis-mirrors-conf }\nhook global BufCreate .*/metadata/licence_groups.conf   %{ set-option buffer filetype exheres-0-licence-groups }\nhook global BufCreate .*/metadata/options/descriptions/.*\\.conf   %{ set-option buffer filetype exheres-0-licence-groups }\nhook global BufCreate .*/metadata/.*\\.conf              %{ set-option buffer filetype exheres-0-metadata }\n\n## News items\nhook global BufCreate .*/metadata/news/.*/.*\\.txt %{ set-option buffer filetype glep42 }\n\n## exheres-0, exlib\nhook global BufCreate .*\\.(exheres-0|exlib) %{ set-option buffer filetype sh }\n\n# Paludis configurations\nhook global BufCreate .*/etc/paludis(-.*)?/bashrc                               %{ set-option buffer filetype sh }\nhook global BufCreate .*/etc/paludis(-.*)?/general(\\.conf.d/.*.conf|\\.conf)     %{ set-option buffer filetype paludis-key-value-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/licences(\\.conf.d/.*.conf|\\.conf)    %{ set-option buffer filetype paludis-options-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/mirrors(\\.conf.d/.*.conf|\\.conf)     %{ set-option buffer filetype paludis-mirrors-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/options(\\.conf.d/.*.conf|\\.conf)     %{ set-option buffer filetype paludis-options-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/output(\\.conf.d/.*.conf|\\.conf)      %{ set-option buffer filetype paludis-key-value-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/package_(unmask|mask)(\\.conf.d/.*.conf|\\.conf)     %{ set-option buffer filetype paludis-specs-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/platforms(\\.conf.d/.*.conf|\\.conf)   %{ set-option buffer filetype paludis-specs-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/repositories/.*\\.conf                %{ set-option buffer filetype paludis-key-value-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/repository\\.template                 %{ set-option buffer filetype paludis-key-value-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/repository_defaults\\.conf            %{ set-option buffer filetype paludis-key-value-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/specpath\\.conf                       %{ set-option buffer filetype paludis-key-value-conf }\nhook global BufCreate .*/etc/paludis(-.*)?/suggestions(\\.conf.d/.*.conf|\\.conf) %{ set-option buffer filetype paludis-specs-conf }\n\nhook global WinSetOption filetype=exheres-0-(metadata|options-descriptions|licence-groups) %{\n    require-module exheres\n}\n\nhook -group exheres-0-metadata-highlight global WinSetOption filetype=exheres-0-metadata %{\n    add-highlighter window/exheres-0-metadata ref exheres-0-metadata\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/exheres-0-metadata }\n}\n\nhook -group exheres-0-options-descriptions-highlight global WinSetOption filetype=exheres-0-options-descriptions %{\n    add-highlighter window/exheres-0-options-descriptions ref exheres-0-options-descriptions\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/exheres-0-options-descriptions }\n}\n\nhook -group exheres-0-licence-groups-highlight global WinSetOption filetype=exheres-0-licence-groups %{\n    add-highlighter window/exheres-0-licence-groups ref exheres-0-licence-groups\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/exheres-0-licence-groups }\n}\n\nprovide-module exheres %{\n# Highlighters\n## exheres-0 Repository metadata files\nadd-highlighter shared/exheres-0-metadata group\nadd-highlighter shared/exheres-0-metadata/ regex ^#.*?$ 0:comment\nadd-highlighter shared/exheres-0-metadata/ regex ^(?:[\\s\\t]+)?(\\*)?(\\S+)(?:[\\s\\t]+)?=(?:[\\s\\t]+)?(.+?)?$ 1:type 2:attribute 3:string\nadd-highlighter shared/exheres-0-metadata/ regex ^(?:[\\s\\t]+)?[\\S]+[\\s\\t]+=[\\s\\t]+\\[.+?[\\s\\t]+\\] 0:string\nadd-highlighter shared/exheres-0-metadata/ regex ^(?:[\\s\\t]+)?(\\S+)\\s\\[\\[$ 0:type\nadd-highlighter shared/exheres-0-metadata/ regex ^(?:[\\s\\t]+)?\\]\\]$ 0:type\n\n## exheres-0 options descriptions\nadd-highlighter shared/exheres-0-options-descriptions group\nadd-highlighter shared/exheres-0-options-descriptions/ regex ^#.*?$ 0:comment\nadd-highlighter shared/exheres-0-options-descriptions/ regex ^(?:[\\s\\t]+)?[\\S]+[\\s\\t]+-[\\s\\t]+\\[.+?[\\s\\t]+\\] 0:string\nadd-highlighter shared/exheres-0-options-descriptions/ regex ^(?:[\\s\\t]+)?(\\S+)\\s\\[\\[$ 0:type\nadd-highlighter shared/exheres-0-options-descriptions/ regex ^(?:[\\s\\t]+)?\\]\\]$ 0:type\n\n## metadata/licence_groups.conf\nadd-highlighter shared/exheres-0-licence-groups group\nadd-highlighter shared/exheres-0-licence-groups/ regex [\\s\\t]+(\\S+(?:[\\s\\t]+))*$ 0:attribute\nadd-highlighter shared/exheres-0-licence-groups/ regex ^(\\S+) 0:type\nadd-highlighter shared/exheres-0-licence-groups/ regex ^#.*?$ 0:comment\n}\n\nhook global WinSetOption filetype=paludis-(key-value|options|mirrors|specs)-conf %{\n    require-module paludis\n}\n\nhook -group paludis-options-conf-highlight global WinSetOption filetype=paludis-options-conf %{\n    add-highlighter window/paludis-options-conf ref paludis-options-conf\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/paludis-options-conf }\n}\n\nhook -group paludis-key-value-conf-highlight global WinSetOption filetype=paludis-key-value-conf %{\n    add-highlighter window/paludis-key-value-conf ref paludis-key-value-conf\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/paludis-key-value-conf }\n}\n\nhook -group paludis-mirrors-conf-highlight global WinSetOption filetype=paludis-mirrors-conf %{\n    add-highlighter window/paludis-mirrors-conf ref paludis-mirrors-conf\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/paludis-mirrors-conf }\n}\n\nhook -group paludis-specs-conf-highlight global WinSetOption filetype=paludis-specs-conf %{\n    add-highlighter window/paludis-specs-conf ref paludis-specs-conf\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/paludis-specs-conf }\n}\nprovide-module paludis %{\n## Paludis configurations\n### options.conf\nadd-highlighter shared/paludis-options-conf group\nadd-highlighter shared/paludis-options-conf/ regex [\\s\\t]+(\\S+(?:[\\s\\t]+))*$ 0:attribute\nadd-highlighter shared/paludis-options-conf/ regex (?::)(?:[\\s\\t]+)(.*?$) 1:attribute\nadd-highlighter shared/paludis-options-conf/ regex [\\s\\t]+(\\S+=)(\\S+) 1:attribute 2:value\nadd-highlighter shared/paludis-options-conf/ regex [\\s\\t](\\S+:) 0:keyword\nadd-highlighter shared/paludis-options-conf/ regex [\\s\\t](-\\S+)(.*?) 1:red\nadd-highlighter shared/paludis-options-conf/ regex ^(\\S+/\\S+) 0:type\nadd-highlighter shared/paludis-options-conf/ regex ^#.*?$ 0:comment\n\n## general.conf, repository.template\nadd-highlighter shared/paludis-key-value-conf group\nadd-highlighter shared/paludis-key-value-conf/ regex ^[\\s\\t]?(\\S+)[\\s\\t+]=[\\s\\t+](.*?)$ 1:attribute 2:value\nadd-highlighter shared/paludis-key-value-conf/ regex ^#.*?$ 0:comment\n\n## mirrors.conf\nadd-highlighter shared/paludis-mirrors-conf group\nadd-highlighter shared/paludis-mirrors-conf/ regex ^[\\s\\t+]?(\\S+)[\\s\\t+](.*?)$ 1:type 2:value\nadd-highlighter shared/paludis-mirrors-conf/ regex ^#.*?$ 0:comment\n\n## package_(unmask|mask).conf, platforms.conf\nadd-highlighter shared/paludis-specs-conf group\nadd-highlighter shared/paludis-specs-conf/ regex [\\s\\t]+(\\S+(?:[\\s\\t]+))*$ 0:attribute\nadd-highlighter shared/paludis-specs-conf/ regex ^(\\S+/\\S+) 0:type\nadd-highlighter shared/paludis-specs-conf/ regex ^#.*?$ 0:comment\n}\n\nhook global WinSetOption filetype=glep42 %{\n    require-module glep42\n}\n\nhook -group glep42-highlight global WinSetOption filetype=glep42 %{\n    add-highlighter window/glep42 ref glep42\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/glep42 }\n}\n\nprovide-module glep42 %{\n## News items (GLEP42)\nadd-highlighter shared/glep42 group\nadd-highlighter shared/glep42/ regex ^(Title|Author|Translator|Content-Type|Posted|Revision|News-Item-Format|Display-If-Installed|Display-If-Keyword|Display-If-Profile):([^\\n]*(?:\\n\\h+[^\\n]+)*)$ 1:keyword 2:attribute\nadd-highlighter shared/glep42/ regex <[^@>]+@.*?> 0:string\nadd-highlighter shared/glep42/ regex ^>.*?$ 0:comment\n}\n"
  },
  {
    "path": "rc/filetype/fennel.kak",
    "content": "# http://fennel-lang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.]fnl %{\n    set-option buffer filetype fennel\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\nhook global WinSetOption filetype=fennel %{\n    require-module fennel\n    fennel-configure-window\n}\n\nhook -group fennel-highlight global WinSetOption filetype=fennel %{\n    add-highlighter window/fennel ref fennel\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/fennel }\n}\n\nprovide-module fennel %{\n\nrequire-module lisp\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/fennel regions\nadd-highlighter shared/fennel/code default-region group\nadd-highlighter shared/fennel/comment region '(?<!\\\\)(?:\\\\\\\\)*\\K;' '$' fill comment\nadd-highlighter shared/fennel/shebang region '(?<!\\\\)(?:\\\\\\\\)*\\K#!' '$' fill comment\nadd-highlighter shared/fennel/string region '(?<!\\\\)(?:\\\\\\\\)*\\K\"' '(?<!\\\\)(?:\\\\\\\\)*\"' fill string\nadd-highlighter shared/fennel/colon-string region '\\W\\K:[-\\w?\\\\^_!$%&*+./@|<=>]' '(?![-\\w?\\\\^_!$%&*+./@|<=>])' fill keyword\nadd-highlighter shared/fennel/code/ regex \\\\(?:space|tab|newline|return|backspace|formfeed|u[0-9a-fA-F]{4}|o[0-3]?[0-7]{1,2}|.)\\b 0:string\n\nevaluate-commands %sh{\n    # Grammar\n    keywords=\"require-macros eval-compiler doc lua hashfn macro macros import-macros pick-args pick-values macroexpand macrodebug\n              do values if when each for fn lambda λ partial while set global var local let tset set-forcibly! doto match or and\n              not not= collect icollect accumulate rshift lshift bor band bnot bxor with-open\"\n    re_keywords='\\\\$ \\\\$1 \\\\$2 \\\\$3 \\\\$4 \\\\$5 \\\\$6 \\\\$7 \\\\$8 \\\\$9 \\\\$\\\\.\\\\.\\\\.'\n    builtins=\"_G _VERSION arg assert bit32 collectgarbage coroutine debug\n              dofile error getfenv getmetatable io ipairs length load\n              loadfile loadstring math next os package pairs pcall\n              print rawequal rawget rawlen rawset require select setfenv\n              setmetatable string table tonumber tostring type unpack xpcall\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n# Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list fennel_static_words $(join \"${keywords} ${builtins} false nil true\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/fennel/code/keywords regex \\b($(join \"${keywords} ${re_keywords}\" '|'))\\b 0:keyword\n        add-highlighter shared/fennel/code/builtins regex \\b($(join \"${builtins}\" '|'))\\b 0:builtin\n    \"\n}\n\nadd-highlighter shared/fennel/code/operator regex (\\.|\\?\\.|\\+|\\^|-|\\*|%|/|>|<|>=|<=|=|not=|:|->|->>|-\\?>|-\\?>>)\\s+|\\.\\.\\.? 0:operator\nadd-highlighter shared/fennel/code/value regex (\\b(false|nil|true)\\b|-?\\b[0-9][_0-9]*(:?\\.[_0-9]+)?(:?[eE]-?[_0-9]+)?|0x[0-9a-fA-F]\\b) 0:value\nadd-highlighter shared/fennel/code/function_declaration regex \\((?:fn|lambda|λ)\\s+([\\S]+) 1:function\nadd-highlighter shared/fennel/code/method_call regex (?:\\w+|\\$[0-9]{0,1}):(\\S+)\\b 1:function\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden fennel-configure-window %{\n    set-option window static_words %opt{fennel_static_words}\n\n    hook window ModeChange pop:insert:.* -group fennel-trim-indent fennel-trim-indent\n    hook window InsertChar \\n -group fennel-indent fennel-indent-on-new-line\n\n    set-option buffer extra_word_chars '_' . / * ? + - < > ! : \"'\"\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window fennel-.+ }\n}\n\ndefine-command -hidden fennel-trim-indent lisp-trim-indent\n\ndeclare-option \\\n    -docstring 'regex matching the head of forms which have options *and* indented bodies' \\\n    regex fennel_special_indent_forms \\\n    '(?:if|when|each|for|fn|lambda|λ|partial|while|local|var|doto|let)'\n\ndefine-command -hidden fennel-indent-on-new-line %{\n    # registers: i = best align point so far; w = start of first word of form\n    evaluate-commands -draft -save-regs '/\"|^@iw' -itersel %{\n        execute-keys -draft 'gk\"iZ'\n        try %{\n            execute-keys -draft '[bl\"i<a-Z><gt>\"wZ'\n\n            try %{\n                # If a special form, indent another (indentwidth - 1) spaces\n                execute-keys -draft '\"wze<a-K>[\\s()\\[\\]\\{\\}]<ret><a-k>\\A' %opt{fennel_special_indent_forms} '\\z<ret>'\n                execute-keys -draft '\"wze<a-L>s.{' %sh{printf $(( kak_opt_indentwidth - 1 ))} '}\\K.*<ret><a-;>;\"i<a-Z><gt>'\n            } catch %{\n                # If not special and parameter appears on line 1, indent to parameter\n                execute-keys -draft '\"wz<a-K>[()[\\]{}]<ret>e<a-K>[\\s()\\[\\]\\{\\}]<ret><a-l>s\\h\\K[^\\s].*<ret><a-;>;\"i<a-Z><gt>'\n            }\n        }\n        try %{ execute-keys -draft '[rl\"i<a-Z><gt>' }\n        try %{ execute-keys -draft '[Bl\"i<a-Z><gt>' }\n        execute-keys -draft ';\"i<a-z>a&,'\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/fidl.kak",
    "content": "# Detection\nhook global BufCreate .*\\.fidl %{\n    set-option buffer filetype fidl\n}\n\nhook global WinSetOption filetype=fidl %<\n    require-module fidl\n    hook window ModeChange pop:insert:.* -group fidl-trim-indent fidl-trim-indent\n    hook window InsertChar \\n -group fidl-indent fidl-indent-on-new-line\n    hook window InsertChar [)}] -group fidl-indent fidl-indent-on-closing\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window fidl-.+ }\n>\n\nhook -group fidl-highlight global WinSetOption filetype=fidl %{\n    add-highlighter window/fidl ref fidl\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/fidl }\n}\n\nprovide-module fidl %§\n\n# Highlighters\n\nadd-highlighter shared/fidl regions\nadd-highlighter shared/fidl/code default-region group\n\nadd-highlighter shared/fidl/string region \\\" (?<!\\\\)(\\\\\\\\)*\" fill string\n\nadd-highlighter shared/fidl/line_doc region ///(?!/) $ fill documentation\nadd-highlighter shared/fidl/line_comment region // $ group\nadd-highlighter shared/fidl/line_comment/comment fill comment\nadd-highlighter shared/fidl/line_comment/todo regex TODO|FIXME: 0:meta\n\nadd-highlighter shared/fidl/attributes region @[a-zA-Z] \\b fill meta\n\nadd-highlighter shared/fidl/code/keywords regex \\b(as|bits|compose|const|enum|error|flexible|optional|library|protocol|resource|service|strict|struct|table|type|union|using)\\b 0:keyword\nadd-highlighter shared/fidl/code/types regex \\b(array|bool|bytes?|client_end|float(32|64)|server_end|string|u?int(8|16|32|64)|vector)\\b 0:type\n\nadd-highlighter shared/fidl/code/literals group\nadd-highlighter shared/fidl/code/literals/bool regex \\b(true|false)\\b 0:value\nadd-highlighter shared/fidl/code/literals/decimal regex \\b([0-9]+(\\.[0-9]+)?)\\b 0:value\nadd-highlighter shared/fidl/code/literals/hexadecimal regex \\b(0x[0-9a-fA-F])\\b 0:value\nadd-highlighter shared/fidl/code/literals/binary regex \\b(0b[01])\\b 0:value\n\n# Commands\n\ndefine-command -hidden fidl-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden fidl-indent-on-new-line %~\n    evaluate-commands -draft -itersel %@\n        try %{ # line comment\n            # copy the commenting prefix\n            execute-keys -draft k x s ^\\h*/{2,}\\h* <ret> yjghP\n        } catch %`\n            # preserve previous line indent\n            try %{ execute-keys -draft <semicolon> K <a-&> }\n            # align to opening ( or { if possible\n            try %+\n                execute-keys -draft <a-k> [)}] <ret> m <a-S> 1<a-&>\n            + catch %+\n                # indent after lines ending with ( or {\n                try %! execute-keys -draft k x <a-k> [({]$ <ret> j <a-gt> !\n            +\n        `\n        # remove trailing white spaces\n        try %{ execute-keys -draft k : fidl-trim-indent <ret> }\n    @\n~\n\ndefine-command -hidden fidl-indent-on-closing %~\n    evaluate-commands -draft -itersel %@\n        # align to opening ( or { when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h*[)}]$ <ret> m <a-S> 1<a-&> >\n    @\n~\n\n§\n"
  },
  {
    "path": "rc/filetype/fish.kak",
    "content": "# http://fishshell.com\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](fish) %{\n    set-option buffer filetype fish\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=fish %{\n    require-module fish\n\n    hook window ModeChange pop:insert:.* -group fish-trim-indent fish-trim-indent\n    hook window InsertChar .* -group fish-indent fish-indent-on-char\n    hook window InsertChar \\n -group fish-insert fish-insert-on-new-line\n    hook window InsertChar \\n -group fish-indent fish-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window fish-.+ }\n}\n\nhook -group fish-highlight global WinSetOption filetype=fish %{\n    add-highlighter window/fish ref fish\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/fish }\n}\n\n\nprovide-module fish %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/fish regions\nadd-highlighter shared/fish/code default-region group\nadd-highlighter shared/fish/double_string region (?<!\\\\)(?:\\\\\\\\)*\\K\" (?<!\\\\)(\\\\\\\\)*\"  group\nadd-highlighter shared/fish/single_string region (?<!\\\\)(?:\\\\\\\\)*\\K' (?<!\\\\)(\\\\\\\\)*'  fill string\nadd-highlighter shared/fish/comment       region (?<!\\\\)(?:\\\\\\\\)*(?:^|\\h)\\K# '$' fill comment\n\nadd-highlighter shared/fish/double_string/ fill string\nadd-highlighter shared/fish/double_string/ regex ((?<!\\\\)(?:\\\\\\\\)*\\K\\$\\w+)|(\\{\\$\\w+\\}) 0:variable\n\nadd-highlighter shared/fish/code/ regex (?<!\\\\)(?:\\\\\\\\)*\\K(\\$\\w+)|(\\{\\$\\w+\\}) 0:variable\n\n# Command names are collected using `builtin --names`.\nadd-highlighter shared/fish/code/ regex \\b(?<![$-])(and|argparse|begin|bg|bind|block|break|breakpoint|builtin|case|cd|command|commandline|complete|contains|continue|count|disown|echo|else|emit|end|eval|exec|exit|false|fg|for|function|functions|history|if|jobs|math|not|or|printf|pwd|random|read|realpath|return|set|set_color|source|status|string|switch|test|time|true|ulimit|wait|while)\\b(?!-) 0:keyword\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden fish-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # remove trailing white spaces\n        try %{ execute-keys -draft x 1s^(\\h+)$<ret> d }\n    }\n}\n\ndefine-command -hidden fish-indent-on-char %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # align middle and end structures to start and indent when necessary\n        try %{ execute-keys -draft x<a-k>^\\h*(else)$<ret><a-semicolon><a-?>^\\h*(if)<ret>s\\A|.\\z<ret>1<a-&> }\n        try %{ execute-keys -draft x<a-k>^\\h*(end)$<ret><a-semicolon><a-?>^\\h*(begin|for|function|if|switch|while)<ret>s\\A|.\\z<ret>1<a-&> }\n        try %{ execute-keys -draft x<a-k>^\\h*(case)$<ret><a-semicolon><a-?>^\\h*(switch)<ret>s\\A|.\\z<ret>1<a-&> }\n    }\n}\n\ndefine-command -hidden fish-insert-on-new-line %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # copy '#' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*#\\h* <ret> y jgh P }\n    }\n}\n\ndefine-command -hidden fish-indent-on-new-line %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after start structure\n        try %{ execute-keys -draft kx<a-k>^\\h*(begin|case|else|for|function|if|while)\\b<ret>j<a-gt> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/forth.kak",
    "content": "hook global BufCreate .+\\.(fth|4th|fs|forth) %{\n    set-option buffer filetype forth\n}\n\nhook global WinSetOption filetype=forth %{\n    require-module forth\n}\n\nhook -group forth-highlight global WinSetOption filetype=forth %{\n    add-highlighter window/forth ref forth\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/forth }\n}\n\nprovide-module forth %{\n\nadd-highlighter shared/forth regions\nadd-highlighter shared/forth/comment region '\\(' '\\)' fill comment\n\n}\n"
  },
  {
    "path": "rc/filetype/fsharp.kak",
    "content": "# https://fsharp.org/\n#\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](fs|fsx|fsi) %{\n    set-option buffer filetype fsharp\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=fsharp %{\n    require-module fsharp\n\n    # indent on newline\n    hook window ModeChange pop:insert:.* -group fsharp-trim-indent fsharp-trim-indent\n    hook window InsertChar \\n -group fsharp-insert fsharp-insert-on-new-line\n    hook window InsertChar \\n -group fsharp-indent fsharp-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window fsharp-.+ }\n}\n\nhook -group fsharp-highlight global WinSetOption filetype=fsharp %{\n    add-highlighter window/fsharp ref fsharp\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/fsharp }\n}\n\nprovide-module fsharp %§\n\n# Highlighters & Completion\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/fsharp regions\nadd-highlighter shared/fsharp/code default-region group\nadd-highlighter shared/fsharp/docstring region \\(\\*(?!\\)) (\\*\\)) regions\nadd-highlighter shared/fsharp/double_string region @?(?<!')\" (?<!\\\\)(\\\\\\\\)*\"B? fill string\nadd-highlighter shared/fsharp/comment region '//' '$' fill comment\n# https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/attributes \nadd-highlighter shared/fsharp/attributes region \"\\[<\" \">\\]\" fill meta\n\nadd-highlighter shared/fsharp/docstring/ default-region fill comment\n# ability to write highlighted code inside docstring:\nadd-highlighter shared/fsharp/docstring/ region '>>> \\K' '\\z' ref fsharp\nadd-highlighter shared/fsharp/docstring/ region '\\.\\.\\. \\K' '\\z' ref fsharp\n\nevaluate-commands %sh{\n    # Grammar\n    meta=\"open\"\n\n    # exceptions taken from fsharp.vim colors (https://github.com/fsharp/vim-fsharp)\n    exceptions=\"try|failwith|failwithf|finally|invalid_arg|raise|rethrow\"\n\n    # keywords taken from fsharp.vim colors (https://github.com/fsharp/vim-fsharp)\n    keywords=\"abstract|as|assert|base|begin|class|default|delegate\"\n    keywords=\"${keywords}|do|done|downcast|downto|elif|else|end|exception\"\n    keywords=\"${keywords}|extern|for|fun|function|global|if|in|inherit|inline\"\n    keywords=\"${keywords}|interface|lazy|let|match|member|module|mutable\"\n    keywords=\"${keywords}|namespace|new|of|override|rec|static|struct|then\"\n    keywords=\"${keywords}|to|type|upcast|use|val|void|when|while|with\"\n    keywords=\"${keywords}|async|atomic|break|checked|component|const|constraint\"\n    keywords=\"${keywords}|constructor|continue|decimal|eager|event|external\"\n    keywords=\"${keywords}|fixed|functor|include|method|mixin|object|parallel\"\n    keywords=\"${keywords}|process|pure|return|seq|tailcall|trait|yield\"\n    # additional operator keywords (Microsoft.FSharp.Core.Operators)\n    keywords=\"${keywords}|box|hash|sizeof|nameof|typeof|typedefof|unbox|ref|fst|snd\"\n    keywords=\"${keywords}|stdin|stdout|stderr\"\n    # math operators (Microsoft.FSharp.Core.Operators)\n    keywords=\"${keywords}|abs|acos|asin|atan|atan2|ceil|cos|cosh|exp|floor|log\"\n    keywords=\"${keywords}|log10|pown|round|sign|sin|sinh|sqrt|tan|tanh\"\n\n\n    types=\"array|bool|byte|char|decimal|double|enum|exn|float\"\n    types=\"${types}|float32|int|int16|int32|int64|lazy_t|list|nativeint\"\n    types=\"${types}|obj|option|sbyte|single|string|uint|uint32|uint64\"\n    types=\"${types}|uint16|unativeint|unit\"\n\n    fsharpCoreMethod=\"printf|printfn|sprintf|eprintf|eprintfn|fprintf|fprintfn\"\n\n    # Add the language's grammar to the static completion list\n    printf '%s\\n' \"hook global WinSetOption filetype=fsharp %{\n        set-option window static_words ${values} ${meta} ${exceptions} ${keywords} ${types}\n    }\" | tr '|' ' '\n\n    # Highlight keywords\n    printf '%s\\n' \"\n        add-highlighter shared/fsharp/code/ regex '\\b(${meta})\\b' 0:meta\n        add-highlighter shared/fsharp/code/ regex '\\b(${exceptions})\\b' 0:function\n        add-highlighter shared/fsharp/code/ regex '\\b(${fsharpCoreMethod})\\b' 0:function\n        add-highlighter shared/fsharp/code/ regex '\\b(${keywords})\\b' 0:keyword\n    \"\n}\n\n# computation expression keywords prefixed with !\nadd-highlighter shared/fsharp/code/ regex \"\\w+!\" 0:keyword\n# brackets\nadd-highlighter shared/fsharp/code/ regex \"[\\[\\]\\(\\){}]\" 0:bracket\n# accomodate typically overloaded operators\nadd-highlighter shared/fsharp/code/ regex \"\\B(<<>>|<\\|\\|>)\\B\" 0:operator\n# fsharp operators\nadd-highlighter shared/fsharp/code/ regex \"\\B(->|<-|<=|>=)\\B\" 0:operator\nadd-highlighter shared/fsharp/code/ regex \"(\\b(not)\\b|\\b(and)\\b)\" 0:operator\nadd-highlighter shared/fsharp/code/ regex (?<=[\\w\\s\\d'\"_])((\\?)([><+-/*%=]{1,2})(\\??)|(\\??)([><+-/*%=]{1,2})(\\?)) 0:operator\nadd-highlighter shared/fsharp/code/ regex (?<=[\\w\\s\\d'\"_])((\\|)+>|<(\\|)+|<@|@>|<@@|@@>|:>|:\\?|:=|(!|#)(?=\\w)|:\\?>|\\?|~([-+]|(~){0,2})) 0:operator\nadd-highlighter shared/fsharp/code/ regex (?<=[\\w\\s\\d'\"_])(<>|::|\\h\\|\\h|(\\|\\|)+|@|\\.\\.|<=|>=|(<)+|(>)+|!=|==|\\|\\|\\||(\\^)+|(&)+|\\+|-|(\\*)+|//|/|%+|=) 0:operator\nadd-highlighter shared/fsharp/code/ regex (?<=[\\w\\s\\d'\"_])((?<![=<>!])=(?![=])|[+*-]=) 0:builtin\n# integer literals\nadd-highlighter shared/fsharp/code/ regex %{(?<!\\.)\\b[1-9]('?\\d+)*(u?(l|L|y|s|n)?|[IMm])\\b(?!\\.)} 0:value\nadd-highlighter shared/fsharp/code/ regex %{(?<!\\.)\\b0b[01]('?[01]+)*(u?(l|L|y|s|n)?|[IMm])\\b(?!\\.)} 0:value\nadd-highlighter shared/fsharp/code/ regex %{(?<!\\.)\\b0('?[0-7]+)*(u?(l|L|y|s|n)?|[IMm])\\b(?!\\.)} 0:value\nadd-highlighter shared/fsharp/code/ regex %{(?<!\\.)\\b0x[\\da-fA-F]('?[\\da-fA-F]+)*(u?(l|L|y|s|n)?)?\\b(?!\\.)} 0:value\n# floating point literals\nadd-highlighter shared/fsharp/code/ regex %{(?i)(?<!\\.)\\b\\d('?\\d+)*\\.((lf|[fm])\\b|\\B)(?!\\.)} 0:value\nadd-highlighter shared/fsharp/code/ regex %{(?i)(?<!\\.)\\b\\d('?\\d+)*\\.?e[+-]?\\d('?\\d+)*(lf|[fm])?\\b(?!\\.)} 0:value\nadd-highlighter shared/fsharp/code/ regex %{(?i)(?<!\\.)(\\b(\\d('?\\d+)*)|\\B)\\.\\d('?[\\d]+)*(e[+-]?\\d('?\\d+)*)?(lf|[fm])?\\b(?!\\.)} 0:value\nadd-highlighter shared/fsharp/code/ regex %{(?i)(?<!\\.)\\b0x[\\da-f]('?[\\da-f]+)*((lf|[fm])\\b|\\B)(?!\\.)} 0:value\nadd-highlighter shared/fsharp/code/ regex %{(?i)(?<!\\.)\\b0x[\\da-f]('?[\\da-f]+)*\\.?p[+-]?\\d('?\\d+)*)?(lf|[fm])?\\b(?!\\.)} 0:value\nadd-highlighter shared/fsharp/code/ regex %{(?i)(?<!\\.)\\b0x([\\da-f]('?[\\da-f]+)*)?\\.\\d('?[\\d]+)*(p[+-]?\\d('?\\d+)*)?(lf|[fm])?\\b(?!\\.)} 0:value\n# character literals\nadd-highlighter shared/fsharp/code/char regex %{\\B'((\\\\.)|[^'\\\\])'(\\b(B)|\\B)} 0:value\n# other literals\nadd-highlighter shared/fsharp/code/ regex \"\\b(true|false)\\b\" 0:value\nadd-highlighter shared/fsharp/code/ regex \"\\B(\\(\\))\\B\" 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden fsharp-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden fsharp-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*//\\h* <ret> y jgh P }\n    }\n}\n\ndefine-command -hidden fsharp-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft \\; K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after line ending with =\n        try %{ execute-keys -draft , k x <a-k> =$ <ret> j <a-gt> }\n        # indent after line ending with \"do\"\n        try %{ execute-keys -draft , k x <a-k> \\bdo$ <ret> j <a-gt> }\n    }\n}\n\n§\n"
  },
  {
    "path": "rc/filetype/gas.kak",
    "content": "# Detection\n# ---------\nhook global BufCreate .*\\.(s|S|asm)$ %{\n    set-option buffer filetype gas\n}\n\nhook global WinSetOption filetype=gas %{\n    require-module gas\n\n    hook window ModeChange pop:insert:.* -group gas-trim-indent gas-trim-indent\n    hook window InsertChar \\n -group gas-indent gas-indent-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window gas-.+ }\n}\n\nhook -group gas-highlight global WinSetOption filetype=gas %{\n    add-highlighter window/gas ref gas\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/gas }\n}\n\n\nprovide-module gas %{\n\nadd-highlighter shared/gas regions\nadd-highlighter shared/gas/code default-region group\nadd-highlighter shared/gas/string         region '\"' (?<!\\\\)(\\\\\\\\)*\"        fill string\nadd-highlighter shared/gas/commentMulti   region /\\*       \\*/              fill comment\nadd-highlighter shared/gas/commentSingle1 region '#'       '$'              fill comment\nadd-highlighter shared/gas/commentSingle2 region ';'       '$'              fill comment\n\n# Constant\nadd-highlighter shared/gas/code/ regex (0[xX][0-9a-fA-F]+|\\b[0-9]+)\\b 0:value\n\n# Labels\nadd-highlighter shared/gas/code/ regex ^\\h*([A-Za-z0-9_.-]+): 0:operator\n\n# ARM Directives\nadd-highlighter shared/gas/code/ regex ((^|\\s+)\\.([248]byte|align|arch(_extension)?|arm|bsscantunwind|code|[cf]pu|[dq]n|eabi_attribute|even|extend|ldouble|fnend|fnstart|force_thumb|handlerdata|inst(\\.[nw])?|ltorg|movsp|object_arch|packed|pad|personality(index)?|pool|req|save|setfp|screl32|syntax|thumb(_func|_set)?|tlsdescseq|unreq|unwind_raw|vsave)(\\h+|$)) 0:type\n\n# Assembler Directives\nadd-highlighter shared/gas/code/ regex ((^|\\s+)\\.(abort|ABORT|align|app-file|ascii|asciz|balign[wl]|byte|comm|data|def|desc|dim|double|eject|else|endif|equ|extern|file|fill|float|global|globl|hword|ident|if|include|int|irp|irpc|lcomm|iflags|line|linkonce|ln|mri|list|loc|local|long|macro|nolist|octa|org|print|purgem|p2align[wl]?|psize|quad|rept|sbttl|section|set|short|single|size|skip|space|stab[dns]|string|struct|tag|text|title|type|title|uleb128|val|vtable_entry|weak|word|rodata|zero)(\\h+|$)) 0:type\n\n# Registers\nadd-highlighter shared/gas/code/ regex \\%(([re](ax|bx|cx|dx|si|di|bp|sp))|(al|bl|cl|dl|sil|dil|bpl|spl)|(r[8-9][dwb])|(r1[0-5][dwb])|(cs|ds|es|fs|gs|ss|ip|eflags)|([xy]mm[0-9]|[xy]mm1[0-5]))\\b 0:variable\n\n# General Instructions\nadd-highlighter shared/gas/code/ regex \\\n^\\h*(mov|lea|call|test|cmp)([bwlq])?\\b|\\\n^\\h*(bswap[lq]|cmpxchg[bwlq]|cmpxchg8b|cwt[ld]|movabs([bwlq])?|popa([lw])?|pusha([wl])?)\\b|\\\n^\\h*(and|or|not|xor|sar|sal|shr|shl|sub|add|(i)?mul|(i)?div|inc|dec|adc|sbb)([bwlq])?\\b|\\\n^\\h*(rcl|rcr|rol|ror|shld|shrd)([bwlq])?\\b|\\\n^\\h*(bsf|bsr|bt|btc|btr|bts)([wlq])?\\b|\\\n^\\h*(cmps|lods|movs)([sxbwdq])?\\b|\\\n^\\h*(ret([bwlq])?|[il]ret([dq])?|leave|movzb[wlq]|movzw[lq]|movsb[wlq]|movsw[lq]|movslq|cwt[dl]|clt[sdq]|cqt[od])\\b|\\\n^\\h*set(([bagl])?e|(n)?[zlesgabop]|(n)?(ae|le|ge|be))\\b|\\\n^\\h*(cmovn[eszlgba]|cmov[glab]e|cmov[esglabz]|cmovn[lgba]e)\\b|\\\n^\\h*(jmp|j[esglabzcop]|jn[esglabzcop]|j[glasbp]e|jn[glab]e|j(e)?cxz|jpo)\\b|\\\n^\\h*(aa[adms]|da[as]|xadd[bwlq]|xchg[lwq])\\b|\\\n^\\h*(rep|repnz|repz|scas([qlwb])?|stos([qlwb])?)\\b|\\\n^\\h*(cl[cdi]|cmc|lahf|popf([lwq])?|pushf([lwq])?|sahf|st[cdi])\\b|\\\n^\\h*(l[defgs]s([wl])?|cpuid|nop|ud2|xlat(b)?)\\b|\\\n^\\h*(lea|call|push|pop)([wlq])?\\b|\\\n^\\h*(in|ins([lwb])?|out|outs([lwb])?)\\b|\\\n^\\h*(cb(t)?w|cwde|cdqe|cwd|cdq|cqo|sahf|lahf|por|pxor|movap[ds])\\b|\\\n^\\h*(bound([wl])?|enter|int(o)?|lcall|loop(n)?[ez]|pause)\\b 0:keyword\n\n#Floating Point Instructions\nadd-highlighter shared/gas/code/ regex \\\n^\\h*f(add|sub|mul|com|comp|sub|subr|div|divr|ld|xch|st|nop|stp|ldenv|chs|abs)\\b|\\\n^\\h*f(tst|xam|ldcw|ld1|ld2[te]|ldpi|ld[gn]2|ldz|(n)?stenv|2xm1|yl2x|p(a)?tan)\\b|\\\n^\\h*f(xtract|prem(1)?|(dec|inc)stp|(n)?stcw|yl2xp1|sqrt|sincos|rndint|scale|sin|cos|iadd)\\b|\\\n^\\h*f(cmov[bn]e|cmove|cmovn[beu]|cmovnbe|cmovu|imul|icom|icomp|isub|isubr|icomp)\\b|\\\n^\\h*(div|add|sub|mul|div)[ps]s\\b|\\\n^\\h*(div|add|sub|mul|div)[ps]d\\b|\\\n^\\h*(vmovs[ds]|vmovap[sd])\\b|\\\n^\\h*(vcvtts[ds]2si(q)?|vcvtsi2s[d](q)?|vunpcklps|vcvtps2pd|vmovddup|vcvtpd2psx)\\b|\\\n^\\h*(cvtss2s[di]|cvtsi2s[ds]|cvtsd2s[is]|cvtdq2p[ds]|cvtpd2(dq|pi|ps)|cvtpi2p[ds]|cvtps2p[id])\\b|\\\n^\\h*(cvttp[ds]2dq|cvttp[ds]2pi|cvtts[ds]2si)\\b|\\\n^\\h*(vxorp[sd]|vandp[sd]|ucomis[sd])\\b 0:keyword\n\ndefine-command -hidden gas-trim-indent %{\n    evaluate-commands -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h+$ <ret> d }\n    }\n}\n\ndefine-command -hidden gas-indent-on-new-line %~\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : gas-trim-indent <ret> }\n        # indent after label\n        try %[ execute-keys -draft k x <a-k> :$ <ret> j <a-gt> ]\n    >\n~\n\n}\n"
  },
  {
    "path": "rc/filetype/gdscript.kak",
    "content": "# http://godotengine.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](gd) %{\n    set-option buffer filetype gdscript\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=gdscript %{\n    require-module gdscript\n\n    set-option window static_words %opt{gdscript_static_words}\n\n    hook window InsertChar \\n -group gdscript-insert gdscript-insert-on-new-line\n    hook window InsertChar \\n -group gdscript-indent gdscript-indent-on-new-line\n    # cleanup trailing whitespaces on current line insert end\n    hook window ModeChange pop:insert:.* -group gdscript-trim-indent %{ try %{ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d } }\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window gdscript-.+ }\n}\n\nhook -group gdscript-highlight global WinSetOption filetype=gdscript %{\n    add-highlighter window/gdscript ref gdscript\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/gdscript }\n}\n\nprovide-module gdscript %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/gdscript regions\nadd-highlighter shared/gdscript/code default-region group\n\nadd-highlighter shared/gdscript/string region -match-capture (\"|'|\"\"\"|''') (?<!\\\\)(?:\\\\\\\\)*(\"|'|\"\"\"|''') group\nadd-highlighter shared/gdscript/string/ fill string\nadd-highlighter shared/gdscript/string/ regex \\\\[abfnrtv\\n\\\\]                            0:meta\nadd-highlighter shared/gdscript/string/ regex '%%'                                       0:meta\nadd-highlighter shared/gdscript/string/ regex '%[cs]'                                    0:value\nadd-highlighter shared/gdscript/string/ regex '%0?[+-]?([\\d]*|\\*?)\\.?([\\d]*|\\*?)[dfoxX]' 0:value\nadd-highlighter shared/gdscript/string/ regex '%0?([\\d]*|\\*?)\\.?([\\d]*|\\*?)[+-]?[dfoxX]' 0:value\n\nadd-highlighter shared/gdscript/comment region '#' $ fill comment\n\n# integers\nadd-highlighter shared/gdscript/code/ regex (?i)\\b0b[01]+l?\\b                    0:value\nadd-highlighter shared/gdscript/code/ regex (?i)\\b0x[\\da-f]+l?\\b                 0:value\nadd-highlighter shared/gdscript/code/ regex (?i)\\b0o?[0-7]+l?\\b                  0:value\nadd-highlighter shared/gdscript/code/ regex (?i)\\b([1-9]\\d*|0)l?\\b               0:value\n# floats\nadd-highlighter shared/gdscript/code/ regex \\b\\d+[eE][+-]?\\d+\\b                  0:value\nadd-highlighter shared/gdscript/code/ regex (\\b\\d+)?\\.\\d+\\b                      0:value\nadd-highlighter shared/gdscript/code/ regex \\b\\d+\\.                              0:value\n# functions\nadd-highlighter shared/gdscript/code/ regex _?[a-zA-Z]\\w*\\s*(?=\\()               0:function\nadd-highlighter shared/gdscript/code/ regex (?:func\\h+)(_?\\w+)(?:<[^>]+?>)?\\(    1:function\n# operators\nadd-highlighter shared/gdscript/code/ regex \\+|-|\\*|/|%|=|<|>|&|\\||\\^|~|:=       0:operator\n# constants & enums\nadd-highlighter shared/gdscript/code/ regex \\b[A-Z_][A-Z0-9_]*\\b                 0:variable\n# annotations\nadd-highlighter shared/gdscript/code/ regex @\\w+                                 0:attribute\n# special case of get =, set =\nadd-highlighter shared/gdscript/code/ regex \\b(get)\\h*=\\h*(\\w+)                  1:keyword 2:function\nadd-highlighter shared/gdscript/code/ regex \\b(set)\\h*=\\h*(\\w+)                  1:keyword 2:function\n# keywords and built-ins\nevaluate-commands %sh{\n    keywords=\"as await break breakpoint class class_name const continue elif else enum extends for func if in is match pass return self signal static super var void while\"\n\n    values=\"false true null\"\n\n    # Built in `nushell` with `ls doc/classes | each {open $in.name | get attributes.name} | str join \" \"`, the rest are built similarly\n    builtin_classes=\"AABB AESContext AStar2D AStar3D AStarGrid2D AcceptDialog AnimatableBody2D AnimatableBody3D AnimatedSprite2D AnimatedSprite3D AnimatedTexture Animation AnimationLibrary AnimationNode AnimationNodeAdd2 AnimationNodeAdd3 AnimationNodeAnimation AnimationNodeBlend2 AnimationNodeBlend3 AnimationNodeBlendSpace1D AnimationNodeBlendSpace2D AnimationNodeBlendTree AnimationNodeOneShot AnimationNodeOutput AnimationNodeStateMachine AnimationNodeStateMachinePlayback AnimationNodeStateMachineTransition AnimationNodeSync AnimationNodeTimeScale AnimationNodeTimeSeek AnimationNodeTransition AnimationPlayer AnimationRootNode AnimationTrackEditPlugin AnimationTree Area2D Area3D Array ArrayMesh ArrayOccluder3D AspectRatioContainer AtlasTexture AudioBusLayout AudioEffect AudioEffectAmplify AudioEffectBandLimitFilter AudioEffectBandPassFilter AudioEffectCapture AudioEffectChorus AudioEffectCompressor AudioEffectDelay AudioEffectDistortion AudioEffectEQ AudioEffectEQ10 AudioEffectEQ21 AudioEffectEQ6 AudioEffectFilter AudioEffectHighPassFilter AudioEffectHighShelfFilter AudioEffectInstance AudioEffectLimiter AudioEffectLowPassFilter AudioEffectLowShelfFilter AudioEffectNotchFilter AudioEffectPanner AudioEffectPhaser AudioEffectPitchShift AudioEffectRecord AudioEffectReverb AudioEffectSpectrumAnalyzer AudioEffectSpectrumAnalyzerInstance AudioEffectStereoEnhance AudioListener2D AudioListener3D AudioServer AudioStream AudioStreamGenerator AudioStreamGeneratorPlayback AudioStreamMicrophone AudioStreamPlayback AudioStreamPlaybackPolyphonic AudioStreamPlaybackResampled AudioStreamPlayer AudioStreamPlayer2D AudioStreamPlayer3D AudioStreamPolyphonic AudioStreamRandomizer AudioStreamWAV BackBufferCopy BaseButton BaseMaterial3D Basis BitMap Bone2D BoneAttachment3D BoneMap BoxContainer BoxMesh BoxOccluder3D BoxShape3D Button ButtonGroup CPUParticles2D CPUParticles3D Callable CallbackTweener Camera2D Camera3D CameraAttributes CameraAttributesPhysical CameraAttributesPractical CameraFeed CameraServer CameraTexture CanvasGroup CanvasItem CanvasItemMaterial CanvasLayer CanvasModulate CanvasTexture CapsuleMesh CapsuleShape2D CapsuleShape3D CenterContainer CharFXTransform CharacterBody2D CharacterBody3D CheckBox CheckButton CircleShape2D ClassDB CodeEdit CodeHighlighter CollisionObject2D CollisionObject3D CollisionPolygon2D CollisionPolygon3D CollisionShape2D CollisionShape3D Color ColorPicker ColorPickerButton ColorRect CompressedCubemap CompressedCubemapArray CompressedTexture2D CompressedTexture2DArray CompressedTexture3D CompressedTextureLayered ConcavePolygonShape2D ConcavePolygonShape3D ConeTwistJoint3D ConfigFile ConfirmationDialog Container Control ConvexPolygonShape2D ConvexPolygonShape3D Crypto CryptoKey Cubemap CubemapArray Curve Curve2D Curve3D CurveTexture CurveXYZTexture CylinderMesh CylinderShape3D DTLSServer DampedSpringJoint2D Decal Dictionary DirAccess DirectionalLight2D DirectionalLight3D DisplayServer EditorCommandPalette EditorDebuggerPlugin EditorDebuggerSession EditorExportPlatform EditorExportPlugin EditorFeatureProfile EditorFileDialog EditorFileSystem EditorFileSystemDirectory EditorFileSystemImportFormatSupportQuery EditorImportPlugin EditorInspector EditorInspectorPlugin EditorInterface EditorNode3DGizmo EditorNode3DGizmoPlugin EditorPaths EditorPlugin EditorProperty EditorResourceConversionPlugin EditorResourcePicker EditorResourcePreview EditorResourcePreviewGenerator EditorSceneFormatImporter EditorScenePostImport EditorScenePostImportPlugin EditorScript EditorScriptPicker EditorSelection EditorSettings EditorSpinSlider EditorSyntaxHighlighter EditorTranslationParserPlugin EditorUndoRedoManager EditorVCSInterface EncodedObjectAsID Engine EngineDebugger EngineProfiler Environment Expression FileAccess FileDialog FileSystemDock FlowContainer FogMaterial FogVolume Font FontFile FontVariation GDExtension GDExtensionManager GPUParticles2D GPUParticles3D GPUParticlesAttractor3D GPUParticlesAttractorBox3D GPUParticlesAttractorSphere3D GPUParticlesAttractorVectorField3D GPUParticlesCollision3D GPUParticlesCollisionBox3D GPUParticlesCollisionHeightField3D GPUParticlesCollisionSDF3D GPUParticlesCollisionSphere3D Generic6DOFJoint3D Geometry2D Geometry3D GeometryInstance3D Gradient GradientTexture1D GradientTexture2D GraphEdit GraphNode GridContainer GrooveJoint2D HBoxContainer HFlowContainer HMACContext HScrollBar HSeparator HSlider HSplitContainer HTTPClient HTTPRequest HashingContext HeightMapShape3D HingeJoint3D IP Image ImageFormatLoader ImageFormatLoaderExtension ImageTexture ImageTexture3D ImageTextureLayered ImmediateMesh ImporterMesh ImporterMeshInstance3D Input InputEvent InputEventAction InputEventFromWindow InputEventGesture InputEventJoypadButton InputEventJoypadMotion InputEventKey InputEventMIDI InputEventMagnifyGesture InputEventMouse InputEventMouseButton InputEventMouseMotion InputEventPanGesture InputEventScreenDrag InputEventScreenTouch InputEventShortcut InputEventWithModifiers InputMap InstancePlaceholder IntervalTweener ItemList JNISingleton JSON JSONRPC JavaClass JavaClassWrapper JavaScriptBridge JavaScriptObject Joint2D Joint3D KinematicCollision2D KinematicCollision3D Label Label3D LabelSettings Light2D Light3D LightOccluder2D LightmapGI LightmapGIData LightmapProbe Lightmapper LightmapperRD Line2D LineEdit LinkButton MainLoop MarginContainer Marker2D Marker3D Marshalls Material MenuBar MenuButton Mesh MeshDataTool MeshInstance2D MeshInstance3D MeshLibrary MeshTexture MethodTweener MissingNode MissingResource MovieWriter MultiMesh MultiMeshInstance2D MultiMeshInstance3D MultiplayerAPI MultiplayerAPIExtension MultiplayerPeer MultiplayerPeerExtension Mutex NavigationAgent2D NavigationAgent3D NavigationLink2D NavigationLink3D NavigationMesh NavigationMeshGenerator NavigationObstacle2D NavigationObstacle3D NavigationPathQueryParameters2D NavigationPathQueryParameters3D NavigationPathQueryResult2D NavigationPathQueryResult3D NavigationPolygon NavigationRegion2D NavigationRegion3D NavigationServer2D NavigationServer3D NinePatchRect Node Node2D Node3D Node3DGizmo NodePath ORMMaterial3D OS Object Occluder3D OccluderInstance3D OccluderPolygon2D OfflineMultiplayerPeer OmniLight3D OptimizedTranslation OptionButton PCKPacker PackedByteArray PackedColorArray PackedDataContainer PackedDataContainerRef PackedFloat32Array PackedFloat64Array PackedInt32Array PackedInt64Array PackedScene PackedStringArray PackedVector2Array PackedVector3Array PacketPeer PacketPeerDTLS PacketPeerExtension PacketPeerStream PacketPeerUDP Panel PanelContainer PanoramaSkyMaterial ParallaxBackground ParallaxLayer ParticleProcessMaterial Path2D Path3D PathFollow2D PathFollow3D Performance PhysicalBone2D PhysicalBone3D PhysicalSkyMaterial PhysicsBody2D PhysicsBody3D PhysicsDirectBodyState2D PhysicsDirectBodyState2DExtension PhysicsDirectBodyState3D PhysicsDirectBodyState3DExtension PhysicsDirectSpaceState2D PhysicsDirectSpaceState2DExtension PhysicsDirectSpaceState3D PhysicsDirectSpaceState3DExtension PhysicsMaterial PhysicsPointQueryParameters2D PhysicsPointQueryParameters3D PhysicsRayQueryParameters2D PhysicsRayQueryParameters3D PhysicsServer2D PhysicsServer2DExtension PhysicsServer2DManager PhysicsServer3D PhysicsServer3DExtension PhysicsServer3DManager PhysicsServer3DRenderingServerHandler PhysicsShapeQueryParameters2D PhysicsShapeQueryParameters3D PhysicsTestMotionParameters2D PhysicsTestMotionParameters3D PhysicsTestMotionResult2D PhysicsTestMotionResult3D PinJoint2D PinJoint3D PlaceholderCubemap PlaceholderCubemapArray PlaceholderMaterial PlaceholderMesh PlaceholderTexture2D PlaceholderTexture2DArray PlaceholderTexture3D PlaceholderTextureLayered Plane PlaneMesh PointLight2D PointMesh Polygon2D PolygonOccluder3D PolygonPathFinder Popup PopupMenu PopupPanel PortableCompressedTexture2D PrimitiveMesh PrismMesh ProceduralSkyMaterial ProgressBar ProjectSettings Projection PropertyTweener QuadMesh QuadOccluder3D Quaternion RDAttachmentFormat RDFramebufferPass RDPipelineColorBlendState RDPipelineColorBlendStateAttachment RDPipelineDepthStencilState RDPipelineMultisampleState RDPipelineRasterizationState RDPipelineSpecializationConstant RDSamplerState RDShaderFile RDShaderSPIRV RDShaderSource RDTextureFormat RDTextureView RDUniform RDVertexAttribute RID RandomNumberGenerator Range RayCast2D RayCast3D Rect2 Rect2i RectangleShape2D RefCounted ReferenceRect ReflectionProbe RemoteTransform2D RemoteTransform3D RenderingDevice RenderingServer Resource ResourceFormatLoader ResourceFormatSaver ResourceImporter ResourceLoader ResourcePreloader ResourceSaver ResourceUID RibbonTrailMesh RichTextEffect RichTextLabel RigidBody2D RigidBody3D RootMotionView SceneState SceneTree SceneTreeTimer Script ScriptCreateDialog ScriptEditor ScriptEditorBase ScriptExtension ScriptLanguage ScriptLanguageExtension ScrollBar ScrollContainer SegmentShape2D Semaphore SeparationRayShape2D SeparationRayShape3D Separator Shader ShaderGlobalsOverride ShaderInclude ShaderMaterial Shape2D Shape3D ShapeCast2D ShapeCast3D Shortcut Signal Skeleton2D Skeleton3D SkeletonIK3D SkeletonModification2D SkeletonModification2DCCDIK SkeletonModification2DFABRIK SkeletonModification2DJiggle SkeletonModification2DLookAt SkeletonModification2DPhysicalBones SkeletonModification2DStackHolder SkeletonModification2DTwoBoneIK SkeletonModificationStack2D SkeletonProfile SkeletonProfileHumanoid Skin SkinReference Sky Slider SliderJoint3D SoftBody3D SphereMesh SphereOccluder3D SphereShape3D SpinBox SplitContainer SpotLight3D SpringArm3D Sprite2D Sprite3D SpriteBase3D SpriteFrames StandardMaterial3D StaticBody2D StaticBody3D StreamPeer StreamPeerBuffer StreamPeerExtension StreamPeerGZIP StreamPeerTCP StreamPeerTLS String StringName StyleBox StyleBoxEmpty StyleBoxFlat StyleBoxLine StyleBoxTexture SubViewport SubViewportContainer SurfaceTool SyntaxHighlighter SystemFont TCPServer TLSOptions TabBar TabContainer TextEdit TextLine TextMesh TextParagraph TextServer TextServerDummy TextServerExtension TextServerManager Texture Texture2D Texture2DArray Texture3D TextureButton TextureLayered TextureProgressBar TextureRect Theme ThemeDB Thread TileData TileMap TileMapPattern TileSet TileSetAtlasSource TileSetScenesCollectionSource TileSetSource Time Timer TorusMesh TouchScreenButton Transform2D Transform3D Translation TranslationServer Tree TreeItem TriangleMesh TubeTrailMesh Tween Tweener UDPServer UndoRedo VBoxContainer VFlowContainer VScrollBar VSeparator VSlider VSplitContainer Variant Vector2 Vector2i Vector3 Vector3i Vector4 Vector4i VehicleBody3D VehicleWheel3D VideoStream VideoStreamPlayback VideoStreamPlayer Viewport ViewportTexture VisibleOnScreenEnabler2D VisibleOnScreenEnabler3D VisibleOnScreenNotifier2D VisibleOnScreenNotifier3D VisualInstance3D VisualShader VisualShaderNode VisualShaderNodeBillboard VisualShaderNodeBooleanConstant VisualShaderNodeBooleanParameter VisualShaderNodeClamp VisualShaderNodeColorConstant VisualShaderNodeColorFunc VisualShaderNodeColorOp VisualShaderNodeColorParameter VisualShaderNodeComment VisualShaderNodeCompare VisualShaderNodeConstant VisualShaderNodeCubemap VisualShaderNodeCubemapParameter VisualShaderNodeCurveTexture VisualShaderNodeCurveXYZTexture VisualShaderNodeCustom VisualShaderNodeDerivativeFunc VisualShaderNodeDeterminant VisualShaderNodeDistanceFade VisualShaderNodeDotProduct VisualShaderNodeExpression VisualShaderNodeFaceForward VisualShaderNodeFloatConstant VisualShaderNodeFloatFunc VisualShaderNodeFloatOp VisualShaderNodeFloatParameter VisualShaderNodeFresnel VisualShaderNodeGlobalExpression VisualShaderNodeGroupBase VisualShaderNodeIf VisualShaderNodeInput VisualShaderNodeIntConstant VisualShaderNodeIntFunc VisualShaderNodeIntOp VisualShaderNodeIntParameter VisualShaderNodeIs VisualShaderNodeLinearSceneDepth VisualShaderNodeMix VisualShaderNodeMultiplyAdd VisualShaderNodeOuterProduct VisualShaderNodeOutput VisualShaderNodeParameter VisualShaderNodeParameterRef VisualShaderNodeParticleAccelerator VisualShaderNodeParticleBoxEmitter VisualShaderNodeParticleConeVelocity VisualShaderNodeParticleEmit VisualShaderNodeParticleEmitter VisualShaderNodeParticleMeshEmitter VisualShaderNodeParticleMultiplyByAxisAngle VisualShaderNodeParticleOutput VisualShaderNodeParticleRandomness VisualShaderNodeParticleRingEmitter VisualShaderNodeParticleSphereEmitter VisualShaderNodeProximityFade VisualShaderNodeRandomRange VisualShaderNodeRemap VisualShaderNodeResizableBase VisualShaderNodeSDFRaymarch VisualShaderNodeSDFToScreenUV VisualShaderNodeSample3D VisualShaderNodeScreenUVToSDF VisualShaderNodeSmoothStep VisualShaderNodeStep VisualShaderNodeSwitch VisualShaderNodeTexture VisualShaderNodeTexture2DArray VisualShaderNodeTexture2DArrayParameter VisualShaderNodeTexture2DParameter VisualShaderNodeTexture3D VisualShaderNodeTexture3DParameter VisualShaderNodeTextureParameter VisualShaderNodeTextureParameterTriplanar VisualShaderNodeTextureSDF VisualShaderNodeTextureSDFNormal VisualShaderNodeTransformCompose VisualShaderNodeTransformConstant VisualShaderNodeTransformDecompose VisualShaderNodeTransformFunc VisualShaderNodeTransformOp VisualShaderNodeTransformParameter VisualShaderNodeTransformVecMult VisualShaderNodeUIntConstant VisualShaderNodeUIntFunc VisualShaderNodeUIntOp VisualShaderNodeUIntParameter VisualShaderNodeUVFunc VisualShaderNodeUVPolarCoord VisualShaderNodeVarying VisualShaderNodeVaryingGetter VisualShaderNodeVaryingSetter VisualShaderNodeVec2Constant VisualShaderNodeVec2Parameter VisualShaderNodeVec3Constant VisualShaderNodeVec3Parameter VisualShaderNodeVec4Constant VisualShaderNodeVec4Parameter VisualShaderNodeVectorBase VisualShaderNodeVectorCompose VisualShaderNodeVectorDecompose VisualShaderNodeVectorDistance VisualShaderNodeVectorFunc VisualShaderNodeVectorLen VisualShaderNodeVectorOp VisualShaderNodeVectorRefract VoxelGI VoxelGIData WeakRef Window WorkerThreadPool World2D World3D WorldBoundaryShape2D WorldBoundaryShape3D WorldEnvironment X509Certificate XMLParser XRAnchor3D XRCamera3D XRController3D XRInterface XRInterfaceExtension XRNode3D XROrigin3D XRPose XRPositionalTracker XRServer bool float int\"\n    \n    builtin_methods=\"abs absf absi acos asin atan atan2 bezier_derivative bezier_interpolate bytes_to_var bytes_to_var_with_objects ceil ceilf ceili clamp clampf clampi cos cosh cubic_interpolate cubic_interpolate_angle cubic_interpolate_angle_in_time cubic_interpolate_in_time db_to_linear deg_to_rad ease error_string exp floor floorf floori fmod fposmod hash instance_from_id inverse_lerp is_equal_approx is_finite is_inf is_instance_id_valid is_instance_valid is_nan is_same is_zero_approx lerp lerp_angle lerpf linear_to_db log max maxf maxi min minf mini move_toward nearest_po2 pingpong posmod pow print print_rich print_verbose printerr printraw prints printt push_error push_warning rad_to_deg rand_from_seed randf randf_range randfn randi randi_range randomize remap rid_allocate_id rid_from_int64 round roundf roundi seed sign signf signi sin sinh smoothstep snapped snappedf snappedi sqrt step_decimals str str_to_var tan tanh typeof var_to_bytes var_to_bytes_with_objects var_to_str weakref wrap wrapf wrapi\"\n\n    gdscript_methods=\"Color8 assert char convert dict_to_inst get_stack inst_to_dict is_instance_of len load preload print_debug print_stack range type_exists\"\n\n    gdscript_constants=\"PI TAU INF NAN\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list gdscript_static_words $(join \"${keywords} ${values} ${builtin_classes} ${builtin_methods} ${gdscript_methods} ${gdscript_constants}\" ' ')\"\n\n    printf %s \"\n        add-highlighter shared/gdscript/code/ regex '\\b($(join \"${keywords}\" '|'))\\b'           0:keyword\n        add-highlighter shared/gdscript/code/ regex '\\b($(join \"${values}\" '|'))\\b'             0:value\n        add-highlighter shared/gdscript/code/ regex '\\b($(join \"${builtin_classes}\" '|'))\\b'    0:type\n        add-highlighter shared/gdscript/code/ regex '\\b($(join \"${builtin_methods}\" '|'))\\b\\('  1:builtin\n        add-highlighter shared/gdscript/code/ regex '\\b($(join \"${gdscript_methods}\" '|'))\\b\\(' 1:builtin\n        add-highlighter shared/gdscript/code/ regex '\\b($(join \"${gdscript_constants}\" '|'))\\b' 0:keyword\n    \"\n}\n# nodes\nadd-highlighter shared/gdscript/code/ regex \\$[\\w/]+\\b                           0:module\nadd-highlighter shared/gdscript/code/ regex \\%\\w+(?!/)\\b                         0:string\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden gdscript-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy '#' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*#\\h* <ret> y jgh P }\n    }\n}\n\ndefine-command -hidden gdscript-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after line ending with :\n        try %{ execute-keys -draft , k x <a-k> :$ <ret> <a-K> ^\\h*# <ret> j <a-gt> }\n        # deindent closing brace/bracket when after cursor (for arrays and dictionaries)\n        try %< execute-keys -draft x <a-k> ^\\h*[}\\]] <ret> gh / [}\\]] <ret> m <a-S> 1<a-&> >\n    >\n>\n\n§\n"
  },
  {
    "path": "rc/filetype/gentoo-linux.kak",
    "content": "# portage ebuild file\nhook global BufCreate .*\\.ebuild %{\n    set-option buffer filetype sh\n}\n"
  },
  {
    "path": "rc/filetype/git.kak",
    "content": "hook global BufCreate .*(COMMIT_EDITMSG|MERGE_MSG) %{\n    set-option buffer filetype git-commit\n}\n\nhook global BufCreate .*/NOTES_EDITMSG %{\n    set-option buffer filetype git-notes\n}\n\nhook global BufCreate .*(\\.git(config|modules)|git/config) %{\n    set-option buffer filetype ini\n}\n\nhook global BufCreate .*\\.gitignore %{\n    set-option buffer filetype git-ignore\n}\n\nhook global BufCreate .*git-rebase-todo %{\n    set-option buffer filetype git-rebase\n}\n\nhook global WinSetOption filetype=git-(commit|ignore|notes|rebase) %{\n    require-module \"git-%val{hook_param_capture_1}\"\n}\n\nhook -group git-commit-highlight global WinSetOption filetype=git-commit %{\n    add-highlighter window/git-commit ref git-commit\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/git-commit }\n}\n\nhook -group git-ignore-highlight global WinSetOption filetype=git-ignore %{\n    add-highlighter window/git-ignore ref git-ignore\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/git-ignore }\n}\n\nhook -group git-notes-highlight global WinSetOption filetype=git-notes %{\n    add-highlighter window/git-notes ref git-notes\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/git-notes }\n}\n\nhook -group git-rebase-highlight global WinSetOption filetype=git-rebase %{\n    add-highlighter window/git-rebase ref git-rebase\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/git-rebase }\n}\n\nprovide-module git-commit %{\nrequire-module diff\nadd-highlighter shared/git-commit regions\nadd-highlighter shared/git-commit/diff region '^diff --git' '^(?=diff --git)' ref diff # highlight potential diffs from the -v option\nadd-highlighter shared/git-commit/comments region ^# $ group\nadd-highlighter shared/git-commit/comments/ fill comment\nadd-highlighter shared/git-commit/comments/ regex \"\\b(?:(modified)|(deleted)|(new file)|(renamed|copied)):([^\\n]*)$\" 1:yellow 2:red 3:green 4:blue 5:magenta\n}\n\nprovide-module git-ignore %{\nadd-highlighter shared/git-ignore group\nadd-highlighter shared/git-ignore/glob regex '(?<!\\\\)(?:\\\\\\\\)*\\K(\\*\\*?|\\?|\\[.*?(?<!\\\\)(?:\\\\\\\\)*\\])' 0:operator\nadd-highlighter shared/git-ignore/negate regex '^!' 0:operator\nadd-highlighter shared/git-ignore/comments regex '^#.*?$' 0:comment\n}\n\nprovide-module git-notes %{\nadd-highlighter shared/git-notes regex ^#[^\\n]*$ 0:comment\n}\n\nprovide-module git-rebase %{\nadd-highlighter shared/git-rebase group\nadd-highlighter shared/git-rebase/ regex \"^\\h*#[^\\n]*\\n\" 0:comment\nadd-highlighter shared/git-rebase/ regex \"^(?:(pick|p)|(edit|reword|squash|fixup|exec|break|drop|label|reset|merge|[ersfxbdltm])) (\\w+)\" 1:keyword 2:value 3:meta\n}\n"
  },
  {
    "path": "rc/filetype/github.kak",
    "content": "hook global BufCreate .*/CODEOWNERS %{\n    set-option buffer filetype codeowners\n}\n\nhook global WinSetOption filetype=codeowners %{\n    require-module codeowners\n}\n\nhook -group codeowners-hightlight global WinSetOption filetype=codeowners %{\n    add-highlighter window/codeowners ref codeowners\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/codeowners }\n}\n\nprovide-module codeowners %{\nadd-highlighter shared/codeowners regions\nadd-highlighter shared/codeowners/comments region ^# $ group\nadd-highlighter shared/codeowners/comments/ fill comment\n}\n"
  },
  {
    "path": "rc/filetype/gjs.kak",
    "content": "# https://github.com/ember-template-imports/ember-template-imports\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](gjs) %{\n    set-option buffer filetype gjs\n}\n\nhook global BufCreate .*[.](gts) %{\n    set-option buffer filetype gts\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=(gjs|gts) %{\n    require-module javascript\n\n    hook window ModeChange pop:insert:.* -group \"%val{hook_param_capture_1}-trim-indent\" javascript-trim-indent\n    hook window InsertChar .* -group \"%val{hook_param_capture_1}-indent\" javascript-indent-on-char\n    hook window InsertChar \\n -group \"%val{hook_param_capture_1}-insert\" javascript-insert-on-new-line\n    hook window InsertChar \\n -group \"%val{hook_param_capture_1}-indent\" javascript-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* \"\n        remove-hooks window %val{hook_param_capture_1}-.+\n    \"\n}\n\nhook -group gjs-highlight global WinSetOption filetype=gjs %{\n    require-module gjs\n    add-highlighter window/gjs ref gjs\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/gjs }\n}\n\nhook -group gts-highlight global WinSetOption filetype=gts %{\n    require-module gts\n    add-highlighter window/gts ref gts\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/gts }\n}\n\n# Modules\n\nprovide-module gjs %{\n    require-module javascript\n    require-module hbs\n    require-module html\n    maybe-add-hbs-to-html\n\n    add-highlighter \"shared/gjs\" regions\n    add-highlighter \"shared/gjs/\" default-region ref javascript\n    add-highlighter \"shared/gjs/hbs\" region '<template>' '</template>' ref html\n}\n\nprovide-module gts %{\n    require-module javascript\n    require-module hbs\n    require-module html\n    maybe-add-hbs-to-html\n\n    add-highlighter \"shared/gts\" regions\n    add-highlighter \"shared/gts/\" default-region ref typescript\n    add-highlighter \"shared/gts/hbs\" region '<template>' '</template>' ref html\n}\n"
  },
  {
    "path": "rc/filetype/gleam.kak",
    "content": "# https://gleam.run/\n#\n# a lot of this file was taken from rc/filetype/go.kak and rc/filetype/hare.kak, thanks everyone !\n\n# Detection\nhook global BufCreate .*\\.gleam %{\n    set-option buffer filetype gleam\n}\n\n# Initialization\nhook global WinSetOption filetype=gleam %<\n    require-module gleam\n    set-option window static_words %opt{gleam_static_words}\n    add-highlighter window/gleam ref gleam\n\n    # remove whitespace when exiting insert mode\n    hook window ModeChange pop:insert:.* -group gleam-trim-indent gleam-remove-white-space\n    # gleam special indent hooks\n    hook window InsertChar \\n -group gleam-indent gleam-indent-new-line\n    hook window InsertChar \\n -group gleam-indent gleam-indent-after-blocks\n    hook window InsertChar \\} -group gleam-indent gleam-unindent-after-brackets\n    hook window InsertChar \\] -group gleam-indent gleam-unindent-after-brackets\n    hook window InsertChar \\) -group gleam-indent gleam-unindent-after-brackets\n    # gleam special construct insert\n    hook window InsertChar \\n -group gleam-auto-insert gleam-insert-comment-on-new-line\n    hook window InsertChar \\n -group gleam-auto-insert gleam-insert-pipeline-on-new-line\n\n    # Uninitialization\n    hook -once -always window WinSetOption filetype=.* %[\n        remove-highlighter window/gleam\n        remove-hooks window gleam-.+\n    ]\n>\n\nprovide-module gleam %§\n    # Highlighters\n    add-highlighter shared/gleam regions\n    add-highlighter shared/gleam/code default-region group\n    add-highlighter shared/gleam/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\n    add-highlighter shared/gleam/single_string region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\n    add-highlighter shared/gleam/comment_line  region '//' $              fill comment\n\n    add-highlighter shared/gleam/code/operator group\n    add-highlighter shared/gleam/code/operator/basic      regex '(?:[-+/*]\\.?|[=%])'  0:operator\n    add-highlighter shared/gleam/code/operator/arrows     regex '(?:<-|[-|]>)'        0:operator\n    add-highlighter shared/gleam/code/operator/comparison regex '(?:[<>]=?\\.?|[=!]=)' 0:operator\n    add-highlighter shared/gleam/code/operator/bool       regex '(?:&&|\\|\\|)'         0:operator\n    add-highlighter shared/gleam/code/operator/misc       regex '(?:\\.\\.|<>|\\|)'      0:operator\n\n    add-highlighter shared/gleam/code/numeric group\n    add-highlighter shared/gleam/code/numeric/natural     regex '0*[1-9](?:_?[0-9])*'                                                           0:value\n    add-highlighter shared/gleam/code/numeric/real        regex '0*[1-9](?:_?[0-9])*(?:\\.(?:0*[1-9](?:_?[0-9])*)?(?:e-?0*[1-9](?:_?[0-9])*)?)?' 0:value\n    add-highlighter shared/gleam/code/numeric/binary      regex '\\b0[bB]0*1(?:_?[01])*\\b'                                                       0:value\n    add-highlighter shared/gleam/code/numeric/octal       regex '\\b0[oO]0*[1-7](?:_?[0-7])*\\b'                                                  0:value\n    add-highlighter shared/gleam/code/numeric/hexadecimal regex '\\b0[xX]0*[1-9a-zA-Z](?:_?[0-9a-zA-Z])*\\b'                                      0:value\n\n    add-highlighter shared/gleam/code/function  regex '([a-z][0-9a-z_]*)\\h*\\('                                                              1:function\n    add-highlighter shared/gleam/code/type      regex '[A-Z][a-zA-Z0-9]*'                                                                   0:type\n    add-highlighter shared/gleam/code/keyword   regex '\\b(as|assert|case|const|else|fn|if|import|let|opaque|panic|pub|todo|try|type|use)\\b' 0:keyword\n    add-highlighter shared/gleam/code/attribute regex '@[a-z][a-z_]*'                                                                       0:attribute\n\n    declare-option str-list gleam_static_words \\\n        as assert case const else fn if import let opaque panic pub todo try type use\n\n\tdefine-command -hidden gleam-remove-white-space %[\n\t\ttry %[ execute-keys -draft -itersel xs \\h+$<ret>d ]\n\t]\n\n    define-command -hidden gleam-indent-new-line %[\n        try %[\n            # align current line with previous indent\n            execute-keys -draft -itersel <semicolon>K<a-&>\n            # remove previous line extra whitespace\n            evaluate-commands -draft -itersel %[\n\t            execute-keys k\n\t            gleam-remove-white-space\n            ]\n        ]\n    ]\n    define-command -hidden gleam-indent-after-blocks %<\n        try %<\n            # if there is already a closing bracket on the new created line, skip indentation\n            execute-keys -draft -itersel xs ^\\h*[\\]})]$<ret>\n        > catch %<\n            try %<\n\t            execute-keys -draft -itersel kxs [[={(]$<ret>j<a-gt>\n            >\n        >\n    >\n\n    define-command -hidden gleam-unindent-after-brackets %[\n        evaluate-commands -draft -itersel -no-hooks -save-regs x %[\n            try %[\n                try %[\n                    execute-keys -draft h{c[{([],[})\\]]<ret>gh<a-i><space>\"xy\n                ] catch %[ set-register x '' ]\n                execute-keys -draft gh<a-i><space>\"xR\n            ]\n        ]\n    ]\n\n    define-command -hidden gleam-insert-comment-on-new-line %[\n        evaluate-commands -draft -itersel -no-hooks -save-regs x %[\n            try %[\n                execute-keys -draft kxs ^\\h*/{2,}\\h*<ret><a-f>/H\"xy\n                execute-keys -draft \\\"xP\n            ]\n        ]\n    ]\n\n    define-command -hidden gleam-insert-pipeline-on-new-line %[\n        evaluate-commands -draft -itersel -no-hooks -save-regs x %[\n            try %[\n                execute-keys -draft kxs ^\\h*\\|>\\h*<ret><a-f>|\"xy\n                execute-keys -draft \\\"xP\n            ]\n        ]\n    ]\n§\n\n"
  },
  {
    "path": "rc/filetype/gluon.kak",
    "content": "# http://gluon-lang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](glu) %{\n    set-option buffer filetype gluon\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=gluon %{\n    require-module gluon\n\n    set-option window extra_word_chars '_' \"'\"\n    hook window ModeChange pop:insert:.* -group gluon-trim-indent gluon-trim-indent\n    hook window InsertChar \\n -group gluon-insert gluon-insert-on-new-line\n    hook window InsertChar \\n -group gluon-indent gluon-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-hooks window gluon-.+\n    }\n}\n\nhook -group gluon-highlight global WinSetOption filetype=gluon %{\n    add-highlighter window/gluon ref gluon\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-highlighter window/gluon\n    }\n}\n\n\nprovide-module gluon %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/gluon regions\nadd-highlighter shared/gluon/code default-region group\nadd-highlighter shared/gluon/string region (?<!')\" (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/gluon/raw_string region -match-capture \\br(#+)\" \\\"(#+) fill string\nadd-highlighter shared/gluon/comment region /\\* \\*/ fill comment\nadd-highlighter shared/gluon/line_comment region // $ fill comment\nadd-highlighter shared/gluon/attribute region -recurse \\[ '#\\[' \\] fill meta\n# balance out bracket ]\n\n# matches hexadecimal literals\nadd-highlighter shared/gluon/code/ regex \\b0x+[A-Fa-f0-9]+ 0:value\n# matches decimal and floating-point literals\nadd-highlighter shared/gluon/code/ regex \\b\\d+([.]\\d+)? 0:value\n\n# matches keywords\nadd-highlighter shared/gluon/code/ regex \\\n    (?<!')\\b(type|if|then|else|match|with|let|rec|do|seq|in)\\b(?!') 0:keyword\n\n# matches macros\nadd-highlighter shared/gluon/code/ regex \\b\\w+! 0:meta\n\n# matches uppercase identifiers: Monad Some\nadd-highlighter shared/gluon/code/ regex \\b[A-Z][\\w']* 0:variable\n\n# matches operators: ... > < <= ^ <*> <$> etc\n# matches dot: .\n# matches keywords:  @ : ->\nadd-highlighter shared/gluon/code/ regex (?<![~<=>|:!?/.@$*&#%+\\^\\-\\\\])[~<=>|:!?/.@$*&#%+\\^\\-\\\\]+ 0:operator\n\n# matches 'x' '\\\\' '\\'' '\\n' '\\0'\n# not incomplete literals: '\\'\nadd-highlighter shared/gluon/code/ regex \\B'([^\\\\]|[\\\\]['\"\\w\\d\\\\])' 0:string\n# this has to come after operators so '-' etc is correct\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden gluon-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden gluon-insert-on-new-line %~\n    evaluate-commands -draft -itersel %_\n        # copy // and /// comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K///?\\h* <ret> y gh j P }\n    _\n~\n\ndefine-command -hidden gluon-indent-on-new-line %~\n    evaluate-commands -draft -itersel %_\n        # preserve previous line indent\n        try %{ execute-keys -draft \\; K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : gluon-trim-indent <ret> }\n        # indent after lines ending with (open) braces, =, ->, condition, rec,\n        # or in\n        try %{ execute-keys -draft \\; k x <a-k> (\\(|\\{|\\[|=|->|\\b(?:then|else|rec|in))$ <ret> j <a-gt> }\n        # deindent closing brace(s) when after cursor\n        try %< execute-keys -draft x <a-k> ^\\h*[})\\]] <ret> gh / \\})\\]] <ret> m <a-S> 1<a-&> >\n    _\n~\n\n§\n"
  },
  {
    "path": "rc/filetype/go.kak",
    "content": "# https://golang.org/\n#\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.go %{\n    set-option buffer filetype go\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=go %{\n    require-module go\n\n    set-option window static_words %opt{go_static_words}\n\n    # cleanup trailing whitespaces when exiting insert mode\n    hook window ModeChange pop:insert:.* -group go-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n    hook window InsertChar \\n -group go-indent go-indent-on-new-line\n    hook window InsertChar \\{ -group go-indent go-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group go-indent go-indent-on-closing-curly-brace\n    hook window InsertChar \\n -group go-comment-insert go-insert-comment-on-new-line\n    hook window InsertChar \\n -group go-closing-delimiter-insert go-insert-closing-delimiter-on-new-line\n\n    alias window alt go-alternative-file\n\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-hooks window go-.+\n        unalias window alt go-alternative-file\n    }\n}\n\nhook -group go-highlight global WinSetOption filetype=go %{\n    add-highlighter window/go ref go\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/go }\n}\n\nprovide-module go %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/go regions\nadd-highlighter shared/go/code default-region group\nadd-highlighter shared/go/back_string region '`' '`' fill string\nadd-highlighter shared/go/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/go/single_string region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/go/comment region /\\* \\*/ fill comment\nadd-highlighter shared/go/comment_line region '//' $ fill comment\n\nadd-highlighter shared/go/code/numeric regex %{-?([0-9]*\\.(?!0[xX]))?\\b([0-9]+|0[xX][0-9a-fA-F]+)\\.?([eE][+-]?[0-9]+)?i?\\b} 0:value\nadd-highlighter shared/go/code/function regex \"\\b(\\w*)\\b\\h*(?:\\[[\\w\\s\\.,]*\\])?\\h*\\(\" 1:function\nadd-highlighter shared/go/code/operator regex \"(\\+|-|\\*|/|%|\\+\\+|--|\\+=|-=|\\*=|/=|%=|==|!=|>|<|>=|<=|&|&&|\\|\\||!|<-|:=|\\.\\.\\.)\" 1:operator\n\nevaluate-commands %sh{\n    # Grammar\n    keywords='break default func interface select case defer go map struct\n              chan else goto package switch const fallthrough if range type\n              continue for import return var'\n    types='any bool byte chan comparable complex128 complex64 error float32 float64 int int16 int32\n           int64 int8 interface intptr map rune string struct uint uint16 uint32 uint64 uint8 uintptr'\n    values='false true nil iota'\n    functions='append cap close complex copy delete imag len make new panic print println real recover'\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list go_static_words $(join \"${keywords} ${attributes} ${types} ${values} ${functions}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/go/code/keyword   regex \\b($(join \"${keywords}\" '|'))\\b 0:keyword\n        add-highlighter shared/go/code/attribute regex \\b($(join \"${attributes}\" '|'))\\b 0:attribute\n        add-highlighter shared/go/code/type      regex \\b($(join \"${types}\" '|'))\\b 0:type\n        add-highlighter shared/go/code/value     regex \\b($(join \"${values}\" '|'))\\b 0:value\n        add-highlighter shared/go/code/builtin   regex \\b($(join \"${functions}\" '|'))\\b 0:builtin\n    \"\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command go-alternative-file -docstring 'Jump to the alternate file (implementation ↔ test)' %{ evaluate-commands %sh{\n    case $kak_buffile in\n        *_test.go)\n            altfile=${kak_buffile%_test.go}.go\n            test ! -f \"$altfile\" && echo \"fail 'implementation file not found'\" && exit\n        ;;\n        *.go)\n            altfile=${kak_buffile%.go}_test.go\n            test ! -f \"$altfile\" && echo \"fail 'test file not found'\" && exit\n        ;;\n        *)\n            echo \"fail 'alternative file not found'\" && exit\n        ;;\n    esac\n    printf \"edit -- '%s'\" \"$(printf %s \"$altfile\" | sed \"s/'/''/g\")\"\n}}\n\ndefine-command -hidden go-indent-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        try %<\n            try %{ # line comment\n                execute-keys -draft kx s ^\\h*// <ret>\n            } catch %{ # block comment\n                execute-keys -draft <a-?> /\\* <ret> <a-K>\\*/<ret>\n            }\n        > catch %<\n            # indent after lines with an unclosed { or (\n            try %< execute-keys -draft [c[({],[)}] <ret> <a-k> \\A[({][^\\n]*\\n[^\\n]*\\n?\\z <ret> j<a-gt> >\n            # indent after a switch's case/default statements\n            try %[ execute-keys -draft kx <a-k> ^\\h*(case|default).*:$ <ret> j<a-gt> ]\n            # deindent closing brace(s) when after cursor\n            try %[ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> ]\n        >\n    =\n~\n\ndefine-command -hidden go-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden go-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\ndefine-command -hidden go-insert-comment-on-new-line %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # copy // comments prefix and following white spaces\n        try %{\n            execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K/{2,}\\h* <ret> y<c-o>P<esc>\n            # check for empty comments and delete them\n            try %{\n                execute-keys kx<a-K>^\\h*//+\\h*$<ret>\n            } catch %{\n                execute-keys Jx_d\n            }\n        }\n    ]\n]\n\ndefine-command -hidden go-insert-closing-delimiter-on-new-line %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # Wisely add '}'.\n        evaluate-commands -save-regs x %[\n            # Save previous line indent in register x.\n            try %[ execute-keys -draft kxs^\\h+<ret>\"xy ] catch %[ reg x '' ]\n            try %[\n                # Validate previous line and that it is not closed yet.\n                execute-keys -draft kx <a-k>^<c-r>x.*\\{\\h*\\(?\\h*$<ret> j}iJx <a-K>^<c-r>x\\)?\\h*\\}<ret>\n                # Insert closing '}'.\n                execute-keys -draft o<c-r>x}<esc>\n                # Delete trailing '}' on the line below the '{'.\n                execute-keys -draft xs\\}$<ret>d\n            ]\n        ]\n\n        # Wisely add ')'.\n        evaluate-commands -save-regs x %[\n            # Save previous line indent in register x.\n            try %[ execute-keys -draft kxs^\\h+<ret>\"xy ] catch %[ reg x '' ]\n            try %[\n                # Validate previous line and that it is not closed yet.\n                execute-keys -draft kx <a-k>^<c-r>x.*\\(\\h*$<ret> J}iJx <a-K>^<c-r>x\\)<ret>\n                # Insert closing ')'.\n                execute-keys -draft o<c-r>x)<esc>\n                # Delete trailing ')' on the line below the '('.\n                execute-keys -draft xs\\)\\h*\\}?\\h*$<ret>d\n            ]\n        ]\n    ]\n]\n\n§\n"
  },
  {
    "path": "rc/filetype/graphql.kak",
    "content": "# http://graphql.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](graphqls?) %{\n    set-option buffer filetype graphql\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=graphql %{\n    require-module graphql\n\n    hook window ModeChange pop:insert:.* -group graphql-trim-indent graphql-trim-indent\n    hook window InsertChar .* -group graphql-indent graphql-indent-on-char\n    hook window InsertChar \\n -group graphql-indent graphql-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window graphql-.+ }\n}\n\nhook -group graphql-highlight global WinSetOption filetype=graphql %{\n    add-highlighter window/graphql ref graphql\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/graphql }\n}\n\n\nprovide-module graphql %§ \n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/graphql regions\nadd-highlighter shared/graphql/code default-region group\nadd-highlighter shared/graphql/line-description region '#' '\\n' fill comment\nadd-highlighter shared/graphql/block-description region '\"\"\"' '\"\"\"' fill comment\nadd-highlighter shared/graphql/description region '\"' '\"\\s*\\n' fill comment\nadd-highlighter shared/graphql/object region -recurse \\{ [{] [}] regions\n\n# Objects\nadd-highlighter shared/graphql/object/line-description region '#' '\\n' fill comment\nadd-highlighter shared/graphql/object/block-description region '\"\"\"' '\"\"\"' fill comment\nadd-highlighter shared/graphql/object/field default-region group\nadd-highlighter shared/graphql/object/field/ regex ([A-Za-z][A-Za-z0-9_-]*)(?:\\([^)]*\\))?\\h*[:{] 1:attribute\nadd-highlighter shared/graphql/object/field/ regex ^\\h*([A-Za-z][A-Za-z0-9_-]*)\\h*$ 1:attribute\n\n# Values\nadd-highlighter shared/graphql/object/field/values regex \\b(true|false|null|\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d*)?)\\b 0:value\nadd-highlighter shared/graphql/object/field/variables regex \\$[a-zA-Z0-9]+\\b 0:variable\n# add-highlighter shared/graphql/object/field/string regex '\"([^\"]|\\\\\")*\"' 0:string\nadd-highlighter shared/graphql/object/field/string regex '\"(?:[^\"\\\\]|\\\\.)*\"' 0:string\n\n# Meta\nadd-highlighter shared/graphql/object/field/directives regex @(?:include|skip) 0:meta\n\n# Attributes\nadd-highlighter shared/graphql/object/field/required regex '(?<=[\\w\\]])(?<bang>!)' bang:operator\nadd-highlighter shared/graphql/object/field/assignment regex '=' 0:operator\n\n# Keywords\nadd-highlighter shared/graphql/code/top-level regex '\\bschema\\b' 0:keyword\nadd-highlighter shared/graphql/code/keywords regex '\\b(?<name>enum|fragment|input|implements|interface|mutation|on|query|scalar|subscription|type|union)\\h+(?:[A-Za-z]\\w*)' name:keyword\n\n# Types\nadd-highlighter shared/graphql/object/field/scalars regex \\b(Boolean|Float|ID|Int|String)\\b 0:type\n\n# Operators\nadd-highlighter shared/graphql/object/field/expand-fragment regex '\\.\\.\\.(?=\\w)' 0:operator\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden graphql-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden graphql-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m <a-S> 1<a-&> >\n    >\n>\n\ndefine-command -hidden graphql-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : graphql-trim-indent <ret> }\n        # indent after lines ending with opener token\n        try %< execute-keys -draft k x <a-k> [[{]\\h*$ <ret> j <a-gt> >\n        # deindent closer token(s) when after cursor\n        try %< execute-keys -draft x <a-k> ^\\h*[}\\]] <ret> gh / [}\\]] <ret> m <a-S> 1<a-&> >\n    >\n>\n\n§ \n"
  },
  {
    "path": "rc/filetype/gren.kak",
    "content": "# http://gren-lang.org/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](gren) %{\n    set-option buffer filetype elm\n}\n\n"
  },
  {
    "path": "rc/filetype/groovy.kak",
    "content": "# Adapted from the file created by Daniel Lewan TeddyDD\n\nhook global BufCreate \"(.+\\.(groovy|gvy|gy|gsh|gradle))|.+[Jj]enkinsfile.*\" %{\n    set-option buffer filetype groovy\n}\n\nhook global WinSetOption filetype=groovy %{\n    require-module groovy\n\n    set-option window static_words %opt{groovy_static_words}\n\n    hook window ModeChange pop:insert:.* -group groovy-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n    hook window InsertChar \\n -group groovy-insert groovy-insert-on-new-line\n    hook window InsertChar \\n -group groovy-indent groovy-indent-on-new-line\n    hook window InsertChar \\{ -group groovy-indent groovy-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group groovy-indent groovy-indent-on-closing-curly-brace\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window groovy-.+ }\n}\n\nhook -group groovy-highlight global WinSetOption filetype=groovy %{\n    add-highlighter window/groovy ref groovy\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/groovy }\n}\n\nprovide-module groovy %§\n\nadd-highlighter shared/groovy regions\n\nadd-highlighter shared/groovy/code default-region group\nadd-highlighter shared/groovy/triple_quote region '\"{3}'   (?<!\\\\)(\\\\\\\\)*\"{3}  fill string\nadd-highlighter shared/groovy/double_string region '\"'   (?<!\\\\)(\\\\\\\\)*\"  fill string\nadd-highlighter shared/groovy/single_string region \"'\"   (?<!\\\\)(\\\\\\\\)*'  fill string\nadd-highlighter shared/groovy/comment1 region '/\\*[^*]?' '\\*/' fill comment\nadd-highlighter shared/groovy/comment2 region '//[^/]?' $ fill comment\nadd-highlighter shared/groovy/shellbang region '#!.+' $ fill comment\nadd-highlighter shared/groovy/dollar_string region \"\\$/\" \"(?<!\\$)/\\$\"   fill string\n# add-highlighter shared/groovy/code/identifiers regex '\\b[$_]?[a-zA-Z0-9_]+\\b' 0:variable\nadd-highlighter shared/groovy/code/declaration regex \"(?<typ>\\w+)(?:\\[.*?\\])?\\s+(\\$?\\w+)\\s=\" typ:type\nadd-highlighter shared/groovy/code/numbers regex '\\b[-+]?0x[A-Fa-f0-9_]+[.A-Fa-f0-9_p]*[lLiDgGIF]?|\\b[-+]?[\\d]+b?[.p_\\dEe]*[lLiDgGIF]?' 0:value\nadd-highlighter shared/groovy/slashy_string region \"\\b/\\w\" \"(?<!\\\\)\\w/\\b\"   fill string\n\nevaluate-commands %sh{\n  keywords=\"as|assert|break|case|catch|class|const|continue|def|default\"\n  keywords=\"${keywords}|do|else|enum|extends|finally|for|goto|if|implements|import|in\"\n  keywords=\"${keywords}|instanceof|interface|new|package|return|super|switch|this|throw\"\n  keywords=\"${keywords}|throws|trait|try|while\"\n  builtins=\"true|false|null\"\n  types=\"byte|char|short|int|long|BigInteger|float|double|BigDecimal|boolean\"\n\n  printf %s \"\n    add-highlighter shared/groovy/code/keyword regex \\b(${keywords})\\b 0:keyword\n    add-highlighter shared/groovy/code/builtin regex \\b(${builtins})\\b 0:builtin\n    add-highlighter shared/groovy/code/types   regex \\b(${types})\\b    0:type\n\n    declare-option str-list groovy_static_words \\\"${keywords}|${types}|${builtins}\\\"\n  \"\n}\n\ndefine-command -hidden groovy-insert-on-new-line %[\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K/{2,}\\h* <ret> y<c-o>P<esc> }\n]\n\ndefine-command -hidden groovy-indent-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # indent after lines ending with { or (\n        try %[ execute-keys -draft kx <a-k> [{(]\\h*$ <ret> j<a-gt> ]\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        # align to opening paren of previous line\n        try %{ execute-keys -draft [( <a-k> \\A\\([^\\n]+\\n[^\\n]*\\n?\\z <ret> s \\A\\(\\h*.|.\\z <ret> '<a-;>' & }\n        # indent after a switch's case/default statements\n        try %[ execute-keys -draft kx <a-k> ^\\h*(case|default).*:$ <ret> j<a-gt> ]\n        # indent after keywords\n        try %[ execute-keys -draft <semicolon><a-F>)MB <a-k> \\A(if|else|while|for|try|catch)\\h*\\(.*\\)\\h*\\n\\h*\\n?\\z <ret> s \\A|.\\z <ret> 1<a-&>1<a-space><a-gt> ]\n        # deindent closing brace(s) when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> ]\n    =\n~\n\ndefine-command -hidden groovy-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden groovy-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n§\n"
  },
  {
    "path": "rc/filetype/haml.kak",
    "content": "# http://haml.info\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](haml) %{\n    set-option buffer filetype haml\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=haml %{\n    require-module haml\n\n    hook window ModeChange pop:insert:.* -group haml-trim-indent haml-trim-indent\n    hook window InsertChar \\n -group haml-insert haml-insert-on-new-line\n    hook window InsertChar \\n -group haml-indent haml-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window haml-.+ }\n}\n\nhook -group haml-highlight global WinSetOption filetype=haml %{\n    add-highlighter window/haml ref haml\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/haml }\n}\n\n\nprovide-module haml %[\nrequire-module ruby\nrequire-module coffee\nrequire-module sass\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/haml regions\nadd-highlighter shared/haml/code    default-region group\nadd-highlighter shared/haml/comment region ^\\h*/          $             fill comment\n\n# Filters\n# http://haml.info/docs/yardoc/file.REFERENCE.html#filters\nadd-highlighter shared/haml/eval1   region -recurse \\{ ^\\h*%([A-Za-z][A-Za-z0-9_-]*)([#.][A-Za-z][A-Za-z0-9_-]*)?\\{\\K|#\\{\\K (?=\\}) ref ruby\nadd-highlighter shared/haml/eval2   region ^\\h*[=-]\\K     (?<!\\|)(?=\\n) ref ruby\nadd-highlighter shared/haml/coffee  region ^\\h*:coffee\\K  ^\\h*[%=-]\\K   ref coffee\nadd-highlighter shared/haml/sass    region ^\\h*:sass\\K    ^\\h*[%=-]\\K   ref sass\n\nadd-highlighter shared/haml/code/ regex ^\\h*(:[a-z]+|-|=)|^(!!!)$ 0:meta\nadd-highlighter shared/haml/code/ regex ^\\h*%([A-Za-z][A-Za-z0-9_-]*)([#.][A-Za-z][A-Za-z0-9_-]*)? 1:keyword 2:variable\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden haml-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden haml-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy '/' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K/\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden haml-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : haml-trim-indent <ret> }\n        # indent after lines beginning with : or -\n        try %{ execute-keys -draft k x <a-k> ^\\h*[:-] <ret> j <a-gt> }\n    }\n}\n\n]\n"
  },
  {
    "path": "rc/filetype/hare.kak",
    "content": "# detection\nhook global BufCreate .*[.]ha %{\n    set-option buffer filetype hare\n}\n\n# initialisation\nhook global WinSetOption filetype=hare %{\n    require-module hare\n    hook window ModeChange pop:insert:.* -group hare-trim-indent hare-trim-indent\n    hook window InsertChar \\n -group hare-insert hare-insert-on-new-line\n    hook window InsertChar \\n -group hare-indent hare-indent-on-new-line\n    hook window InsertChar \\{ -group hare-indent hare-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group hare-indent hare-indent-on-closing-curly-brace\n}\n\nhook -group hare-highlight global WinSetOption filetype=hare %{\n    add-highlighter window/hare ref hare\n    hook -once -always window WinSetOption filetype=*. %{ remove-highlighter window/hare }\n}\n\n# highlighters\nprovide-module hare %§\n    add-highlighter shared/hare regions\n    add-highlighter shared/hare/code default-region group\n    add-highlighter shared/hare/comment region // $ fill comment\n\n    add-highlighter shared/hare/rawstring region ` ` group\n    add-highlighter shared/hare/rawstring/ fill string\n\n    add-highlighter shared/hare/string region '\"' (?<!\\\\)(\\\\\\\\)*\" group\n    add-highlighter shared/hare/string/ fill string\n    add-highlighter shared/hare/string/ regex '(\\\\0|\\\\a|\\\\b|\\\\f|\\\\n|\\\\r|\\\\t|\\\\v|\\\\\\\\|\\\\\")' 0:meta\n    add-highlighter shared/hare/string/ regex \"\\\\'\" 0:meta\n    add-highlighter shared/hare/string/ regex \"(\\\\x[0-9a-fA-F]{2})\" 0:meta\n    add-highlighter shared/hare/string/ regex \"(\\\\u[0-9a-fA-F]{4})\" 0:meta\n    add-highlighter shared/hare/string/ regex \"(\\\\U[0-9a-fA-F]{8})\" 0:meta\n\n    add-highlighter shared/hare/rune region \"'\" (?<!\\\\)(\\\\\\\\)*' group\n    add-highlighter shared/hare/rune/ fill string\n    add-highlighter shared/hare/rune/ regex \"(\\\\0|\\\\a|\\\\b|\\\\f|\\\\n|\\\\r|\\\\t|\\\\v|\\\\\\\\|\\\\')\" 0:meta\n    add-highlighter shared/hare/rune/ regex '\\\\\"' 0:meta\n    add-highlighter shared/hare/rune/ regex \"(\\\\x[0-9a-fA-F]{2})\" 0:meta\n    add-highlighter shared/hare/rune/ regex \"(\\\\u[0-9a-fA-F]{4})\" 0:meta\n    add-highlighter shared/hare/rune/ regex \"(\\\\U[0-9a-fA-F]{8})\" 0:meta\n\n    # imports\n    add-highlighter shared/hare/code/ regex \"\\buse\\s.*?(?=;)\" 0:module\n    add-highlighter shared/hare/code/ regex \"\\buse\\b\" 0:meta\n\n    # functions\n    add-highlighter shared/hare/code/ regex \"\\b([0-9a-zA-Z_]*)\\h*\\(\" 1:function\n\n    # attributes\n    add-highlighter shared/hare/code/ regex \"@(packed|offset|init|fini|test|noreturn|symbol)\\b\" 0:attribute\n\n    # declarations\n    add-highlighter shared/hare/code/ regex \"\\b(let|export|const)\\b\" 0:meta\n    add-highlighter shared/hare/code/ regex \"\\b(fn|type|def)\\b\" 0:keyword\n\n    # builtins\n    add-highlighter shared/hare/code/ regex \"\\b(len|offset|free|alloc|assert|append|abort|delete|insert|vastart|vaarg|vaend)\\b\" 0:builtin\n    add-highlighter shared/hare/code/ regex \"\\b(as|is)\\b\" 0:builtin\n\n    # types\n    add-highlighter shared/hare/code/ regex \"\\b(struct|union|enum)\\b\" 0:type\n    add-highlighter shared/hare/code/ regex \"\\b(nullable|null|void)\\b\" 0:type\n    add-highlighter shared/hare/code/ regex \"\\b(u8|u16|u32|u64|uint)\\b\" 0:type\n    add-highlighter shared/hare/code/ regex \"\\b(i8|i16|i32|i64|int)\\b\" 0:type\n    add-highlighter shared/hare/code/ regex \"\\b(size|uintptr|char)\\b\" 0:type\n    add-highlighter shared/hare/code/ regex \"\\b(f32|f64)\\b\" 0:type\n    add-highlighter shared/hare/code/ regex \"\\b(str|rune)\\b\" 0:type\n    add-highlighter shared/hare/code/ regex \"\\b(bool)\\b\" 0:type\n    add-highlighter shared/hare/code/ regex \"\\b(valist)\\b\" 0:type\n\n    # literals\n    add-highlighter shared/hare/code/ regex \"\\b(true|false)\\b\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b[0-9]+([eE][-+]?[0-9]+)?(z|(i|u)(8|16|32|64)?)?\\b\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b[0-9]+([eE][-+]?[0-9]+)?((?=e)|(?=u)|(?=i))\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b0b[0-1]+(z|(i|u)(8|16|32|64)?)?\\b\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b0b[0-1]+((?=u)|(?=i))\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b0o[0-7]+(z|(i|u)(8|16|32|64)?)?\\b\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b0o[0-7]+((?=u)|(?=i))\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b0x[0-9a-fA-F]+(z|(i|u)(8|16|32|64)?)?\\b\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b0x[0-9a-fA-F]+((?=u)|(?=i))\" 0:value\n\n    # floats\n    add-highlighter shared/hare/code/ regex \"\\b[0-9]+\\.[0-9]+([eE][-+]?[0-9]+)?(f32|f64)?\\b\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b[0-9]+\\.[0-9]+([eE][-+]?[0-9]+)?((?=e)|(?=f))\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b[0-9]+([eE][-+]?[0-9]+)?(f32|f64)\\b\" 0:value\n    add-highlighter shared/hare/code/ regex \"\\b[0-9]+([eE][-+]?[0-9]+)?(?=f)\" 0:value\n\n    # constants\n    add-highlighter shared/hare/code/ regex \"\\b[0-9A-Z_]*\\b\" 0:value\n\n    # control flow\n    add-highlighter shared/hare/code/ regex \"\\b(for|if|else|switch|match|return|break|continue|defer|yield|case|static)\\b\" 0:keyword\n\n    # operators\n    add-highlighter shared/hare/code/ regex \"(=|\\+|-|\\*|/|<|>|!|\\?|&|\\||\\.\\.(\\.)?)\" 0:operator\n\n    # commands\n    define-command -hidden hare-indent-on-new-line %[ evaluate-commands -draft -itersel %[\n        # preserve indentation on new lines\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # indent after lines ending with { or (\n        try %[ execute-keys -draft kx <a-k> [{(]\\h*$ <ret> j i<tab> ]\n        # cleanup trailing white spaces on the previous line\n        execute-keys -draft k :hare-trim-indent <ret>\n        # indent after match/switch's case statements\n        try %[ execute-keys -draft kx <a-k> case\\h.*=>\\h*$ <ret> j<a-gt> ]\n        # deindent closing brace(s) when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> ]\n    ] ]\n\n    define-command -hidden hare-insert-on-new-line %{ evaluate-commands -draft -itersel %{\n        try %{ evaluate-commands -draft -save-regs '/\"' %{\n            # copy the comment prefix\n            execute-keys -save-regs '' k x s ^\\h*\\K//\\h* <ret> y\n            try %{\n                # paste the comment prefix\n                execute-keys x j x s ^\\h* <ret>P\n            }\n        } }\n        try %{\n            # remove trailing whitespace on the above line\n            execute-keys -draft k :hare-trim-indent <ret>\n        }\n    } }\n\n    define-command -hidden hare-indent-on-opening-curly-brace %[\n        # align indent with opening paren when { is entered on a new line after the closing paren\n        try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n    ]\n\n    define-command -hidden hare-indent-on-closing-curly-brace %[\n        # align to opening curly brace when alone on a line\n        try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n    ]\n\n    define-command -hidden hare-trim-indent %{ evaluate-commands -draft -itersel %{\n        # remove trailing whitespace\n        try %{ execute-keys -draft x s \\h+$ <ret> d }\n    } }\n§\n"
  },
  {
    "path": "rc/filetype/haskell.kak",
    "content": "# http://haskell.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](hs) %{\n    set-option buffer filetype haskell\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=haskell %{\n    require-module haskell\n\n    set-option buffer extra_word_chars '_' \"'\"\n    hook window ModeChange pop:insert:.* -group haskell-trim-indent haskell-trim-indent\n    hook window InsertChar \\n -group haskell-insert haskell-insert-on-new-line\n    hook window InsertChar \\n -group haskell-indent haskell-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window haskell-.+ }\n}\n\nhook -group haskell-highlight global WinSetOption filetype=haskell %{\n    add-highlighter window/haskell ref haskell\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/haskell }\n}\n\n\nprovide-module haskell %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/haskell regions\nadd-highlighter shared/haskell/code default-region group\nadd-highlighter shared/haskell/string       region (?<!'\\\\)(?<!')\"                 (?<!\\\\)(\\\\\\\\)*\"  fill string\nadd-highlighter shared/haskell/macro        region ^\\K#                            (?<!\\\\)\\n        fill meta\nadd-highlighter shared/haskell/pragma       region -recurse \\{- \\{-#               '#-\\}'           fill meta\nadd-highlighter shared/haskell/comment      region -recurse \\{- \\{-                  -\\}            fill comment\nadd-highlighter shared/haskell/line_comment region --(?![!#$%&*+./<>?@\\\\\\^|~=]) $                   fill comment\nadd-highlighter shared/haskell/quasiquote   region \\[\\b[_a-z]['\\w]*#?\\| \\|\\]                        regex \\[\\b[_a-z]['\\w]*#?\\|(.*?)\\|\\] 1:string\n\nadd-highlighter shared/haskell/code/ regex (?<!')\\b0x+[A-Fa-f0-9]+ 0:value\nadd-highlighter shared/haskell/code/ regex (?<!')\\b\\d+([.]\\d+)? 0:value\nadd-highlighter shared/haskell/code/ regex (?<!')\\b(import|hiding|qualified|module)(?!')\\b 0:keyword\nadd-highlighter shared/haskell/code/ regex (?<!')\\b(import)(?!')\\b[^\\n]+(?<!')\\b(as)(?!')\\b 2:keyword\nadd-highlighter shared/haskell/code/ regex (?<!')\\b(class|data|default|deriving|infix|infixl|infixr|instance|module|newtype|pattern|type|where)(?!')\\b 0:keyword\nadd-highlighter shared/haskell/code/ regex (?<!')\\b(case|do|else|if|in|let|mdo|of|proc|rec|then)(?!')\\b 0:attribute\nadd-highlighter shared/haskell/code/ regex (?<!')\\b(type|data)\\b\\s+(\\bfamily\\b)?(?!') 0:keyword\n\n# The complications below is because period has many uses:\n# As function composition operator (possibly without spaces) like \".\" and \"f.g\"\n# Hierarchical modules like \"Data.Maybe\"\n# Qualified imports like \"Data.Maybe.Just\", \"Data.Maybe.maybe\", \"Control.Applicative.<$>\"\n# Quantifier separator in \"forall a . [a] -> [a]\"\n# Enum comprehensions like \"[1..]\" and \"[a..b]\" (making \"..\" and \"Module...\" illegal)\n\n# matches uppercase identifiers:  Monad Control.Monad\n# not non-space separated dot:    Just.const\nadd-highlighter shared/haskell/code/ regex \\b([A-Z]['\\w]*\\.)*[A-Z]['\\w]*(?!['\\w])(?![.a-z]) 0:variable\n\n# matches infix identifier: `mod` `Apa._T'M`\nadd-highlighter shared/haskell/code/ regex `\\b([A-Z]['\\w]*\\.)*[\\w]['\\w]*` 0:operator\n# matches imported operators: M.! M.. Control.Monad.>>\n# not operator keywords:      M... M.->\nadd-highlighter shared/haskell/code/ regex \\b[A-Z]['\\w]*\\.[~<=>|:!?/.@$*&#%+\\^\\-\\\\]+ 0:operator\n# matches dot: .\n# not possibly incomplete import:  a.\n# not other operators:             !. .!\nadd-highlighter shared/haskell/code/ regex (?<![\\w~<=>|:!?/.@$*&#%+\\^\\-\\\\])\\.(?![~<=>|:!?/.@$*&#%+\\^\\-\\\\]) 0:operator\n# matches other operators: ... > < <= ^ <*> <$> etc\n# not dot: .\n# not operator keywords:  @ .. -> :: ~\nadd-highlighter shared/haskell/code/ regex (?<![~<=>|:!?/.@$*&#%+\\^\\-\\\\])[~<=>|:!?/.@$*&#%+\\^\\-\\\\]+ 0:operator\n\n# matches operator keywords: @ ->\nadd-highlighter shared/haskell/code/ regex (?<![~<=>|:!?/.@$*&#%+\\^\\-\\\\])(@|~|<-|->|=>|::|=|:|[|])(?![~<=>|:!?/.@$*&#%+\\^\\-\\\\]) 1:keyword\n# matches: forall [..variables..] .\n# not the variables\nadd-highlighter shared/haskell/code/ regex \\b(forall|∀)\\b[^.\\n]*?(\\.) 1:keyword 2:keyword\n\n# matches 'x' '\\\\' '\\'' '\\n' '\\0'\n# not incomplete literals: '\\'\n# not valid identifiers:   w' _'\nadd-highlighter shared/haskell/code/ regex \\B'([^\\\\]|[\\\\]['\"\\w\\d\\\\])' 0:string\n# this has to come after operators so '-' etc is correct\n\n# matches function names in type signatures\nadd-highlighter shared/haskell/code/ regex ^\\s*(?:where\\s+|let\\s+|default\\s+)?([_a-z]['\\w]*#?(?:,\\s*[_a-z]['\\w]*#?)*)\\s+::\\s 1:meta\n\n# matches deriving strategies\nadd-highlighter shared/haskell/code/ regex \\bderiving\\s+\\b(stock|newtype|anyclass|via)\\b 1:keyword\nadd-highlighter shared/haskell/code/ regex \\bderiving\\b\\s+(?:[A-Z]['\\w]+|\\([',\\w\\s]+?\\))\\s+\\b(via)\\b 1:keyword\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\n# http://en.wikibooks.org/wiki/Haskell/Indentation\n\ndefine-command -hidden haskell-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden haskell-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy -- comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K--\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden haskell-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # align to first clause\n        try %{ execute-keys -draft <semicolon> k x X s ^\\h*(if|then|else)?\\h*(([\\w']+\\h+)+=)?\\h*(case\\h+[\\w']+\\h+of|do|let|where)\\h+\\K.* <ret> s \\A|.\\z <ret> & }\n        # filter previous line\n        try %{ execute-keys -draft k : haskell-trim-indent <ret> }\n        # indent after lines beginning with condition or ending with expression or =(\n        try %{ execute-keys -draft <semicolon> k x <a-k> ^\\h*if|[=(]$|\\b(case\\h+[\\w']+\\h+of|do|let|where)$ <ret> j <a-gt> }\n    }\n}\n\n]\n"
  },
  {
    "path": "rc/filetype/hbs.kak",
    "content": "# http://handlebarsjs.com/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](hbs) %{\n    set-option buffer filetype hbs\n}\n\nhook global WinSetOption filetype=hbs %{\n    require-module hbs\n\n    hook window ModeChange pop:insert:.* -group hbs-trim-indent hbs-trim-indent\n    hook window InsertChar \\n -group hbs-insert hbs-insert-on-new-line\n    hook window InsertChar \\n -group hbs-indent hbs-indent-on-new-line\n    hook window InsertChar .* -group hbs-indent hbs-indent-on-char\n    hook window InsertChar '>' -group hbs-indent html-indent-on-greater-than\n    hook window InsertChar \\n -group hbs-indent html-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window hbs-.+ }\n}\n\nhook -group hbs-highlight global WinSetOption filetype=hbs %{\n    maybe-add-hbs-to-html\n    add-highlighter window/hbs-file ref hbs-file\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/hbs-file }\n}\n\n\nprovide-module hbs %[\n\nrequire-module html\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/hbs regions\nadd-highlighter shared/hbs/comment          region \\{\\{!--  --\\}\\} fill comment\nadd-highlighter shared/hbs/comment_alt      region \\{\\{!      \\}\\} fill comment\nadd-highlighter shared/hbs/block-expression region \\{\\{[#/]   \\}\\} regions\nadd-highlighter shared/hbs/expression       region \\{\\{       \\}\\} regions\n\ndefine-command -hidden add-mutual-highlighters -params 1 %~\n    add-highlighter \"shared/hbs/%arg{1}/code\" default-region group\n    add-highlighter \"shared/hbs/%arg{1}/single-quote\" region '\"'    (?<!\\\\)(\\\\\\\\)*\" fill string\n    add-highlighter \"shared/hbs/%arg{1}/double-quote\" region \"'\"    (?<!\\\\)(\\\\\\\\)*' fill string\n    add-highlighter \"shared/hbs/%arg{1}/code/variable\" regex \\b([\\w-]+)\\b 1:variable\n    add-highlighter \"shared/hbs/%arg{1}/code/attribute\" regex \\b([\\w-]+)= 1:attribute\n    add-highlighter \"shared/hbs/%arg{1}/code/helper\" regex (?:(?:\\{\\{)|\\()([#/]?[\\w-]+(?:/[\\w-]+)*) 1:keyword\n~\n\nadd-mutual-highlighters expression\nadd-mutual-highlighters block-expression\n\nadd-highlighter shared/hbs/block-expression/code/yield regex \\b(as)\\s|[\\w-]+|\\}\\} 1:keyword\n\n\n# wrapper around shared/html highlighter.  The shared/hbs highlighter will be\n# added into shared/html when a buffer of filetype 'hbs' is actively displayed in the window.\nadd-highlighter shared/hbs-file regions\nadd-highlighter shared/hbs-file/html default-region ref html\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden hbs-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden hbs-indent-on-char %[\n    evaluate-commands -draft -itersel %[\n        # de-indent after closing a yielded block tag\n        try %[ execute-keys -draft , <a-h> s ^\\h+\\{\\{/([\\w-.]+(?:/[\\w-.]+)*)\\}\\}$ <ret> {c\\{\\{#<c-r>1,\\{\\{/<c-r>1\\}\\} <ret> s \\A|.\\z <ret> 1<a-&> ]\n    ]\n]\n\ndefine-command -hidden hbs-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy '/' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K/\\h* <ret> y j p }\n    }\n}\n\ndefine-command -hidden hbs-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : hbs-trim-indent <ret> }\n        # indent after lines beginning with : or -\n        try %{ execute-keys -draft k x <a-k> ^\\h*[:-] <ret> j <a-gt> }\n    }\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\ndeclare-option bool hbs_highlighters_enabled false\n\ndefine-command -hidden maybe-add-hbs-to-html %{ evaluate-commands %sh{\n    if [ \"$kak_opt_hbs_highlighters_enabled\" = \"false\" ]; then\n        printf %s \"\n            add-highlighter shared/html/hbs region '\\{\\{' '\\}\\}' ref hbs\n            add-highlighter shared/html/tag/hbs region '\\{\\{' '\\}\\}' ref hbs\n            set-option global hbs_highlighters_enabled true\n        \"\n    fi\n} }\n\n]\n"
  },
  {
    "path": "rc/filetype/hjson.kak",
    "content": "# http://hjson.github.io\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](hjson) %{\n    set-option buffer filetype hjson\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=hjson %{\n    require-module hjson\n\n    hook window ModeChange pop:insert:.* -group hjson-trim-indent hjson-trim-indent\n    hook window InsertChar .* -group hjson-indent hjson-indent-on-char\n    hook window InsertChar \\n -group hjson-indent hjson-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window hjson-.+ }\n}\n\nhook -group hjson-highlight global WinSetOption filetype=hjson %{\n    add-highlighter window/hjson ref hjson\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/hjson }\n}\n\n\nprovide-module hjson %(\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/hjson regions\nadd-highlighter shared/hjson/code default-region group\nadd-highlighter shared/hjson/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/hjson/string2 region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/hjson/triple_string region -match-capture (\"\"\"|''') (?<!\\\\)(?:\\\\\\\\)*(\"\"\"|''') fill string\nadd-highlighter shared/hjson/comment1 region '#' '$' fill comment\nadd-highlighter shared/hjson/comment2 region '//' '$' fill comment\nadd-highlighter shared/hjson/multiline_comment region /\\* \\*/ fill comment\n\nadd-highlighter shared/hjson/code/ regex ^(\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\nadd-highlighter shared/hjson/code/ regex ,(\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\nadd-highlighter shared/hjson/code/ regex \\{(\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\n\nadd-highlighter shared/hjson/code/ regex ^(\\s*[^,:\\[\\]\\{\\}\\s]+):\\s+([^\\{\\}\\[\\]:,][^\\r\\n]*)? 2:string\nadd-highlighter shared/hjson/code/ regex \\{(\\s*[^,:\\[\\]\\{\\}\\s]+):\\s+([^\\{\\}\\[\\]:,][^\\r\\n]*)? 2:string\n\nadd-highlighter shared/hjson/code/ regex \\b(true|false|null|\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d*)?)\\b 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden hjson-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden hjson-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m <a-S> 1<a-&> >\n    >\n>\n\ndefine-command -hidden hjson-indent-on-new-line %{ \n    evaluate-commands -itersel -draft %{\n        execute-keys <semicolon>\n        try %{\n            evaluate-commands -draft -save-regs '/\"' %{\n                # copy the commenting prefix\n                execute-keys -save-regs '' k x1s^(\\h*(//|#)+\\h*)<ret> y\n                try %{\n                    # if the previous comment isn't empty, create a new one\n                    execute-keys x<a-K>^\\h*(//|#)+\\h*$<ret> jxs^\\h*<ret>P\n                } catch %{\n                    # if there is no text in the previous comment, remove it completely\n                    execute-keys d\n                }\n            }\n\n            # trim trailing whitespace on the previous line\n            try %{ execute-keys -draft k x s\\h+$<ret> d }\n        }\n\n        try %{\n            # if the previous line isn't within a comment scope, break\n            execute-keys -draft kx <a-k>^(\\h*/\\*|\\h+\\*(?!/))<ret>\n\n            # find comment opening, validate it was not closed, and check its using star prefixes\n            execute-keys -draft <a-?>/\\*<ret><a-H> <a-K>\\*/<ret> <a-k>\\A\\h*/\\*([^\\n]*\\n\\h*\\*)*[^\\n]*\\n\\h*.\\z<ret>\n\n            try %{\n                # if the previous line is opening the comment, insert star preceeded by space\n                execute-keys -draft kx<a-k>^\\h*/\\*<ret>\n                execute-keys -draft i*<space><esc>\n            } catch %{\n               try %{\n                    # if the next line is a comment line insert a star\n                    execute-keys -draft jx<a-k>^\\h+\\*<ret>\n                    execute-keys -draft i*<space><esc>\n                } catch %{\n                    try %{\n                        # if the previous line is an empty comment line, close the comment scope\n                        execute-keys -draft kx<a-k>^\\h+\\*\\h+$<ret> x1s\\*(\\h*)<ret>c/<esc>\n                    } catch %{\n                        # if the previous line is a non-empty comment line, add a star\n                        execute-keys -draft i*<space><esc>\n                    }\n                }\n            }\n\n            # trim trailing whitespace on the previous line\n            try %{ execute-keys -draft k x s\\h+$<ret> d }\n            # align the new star with the previous one\n            execute-keys Kx1s^[^*]*(\\*)<ret>&\n        }\n    }\n}\n\n)\n\n"
  },
  {
    "path": "rc/filetype/html.kak",
    "content": "# http://w3.org/html\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.html %{\n    set-option buffer filetype html\n}\n\nhook global BufCreate .*\\.xml %{\n    set-option buffer filetype xml\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=(html|xml) %{\n    require-module html\n\n    hook window ModeChange pop:insert:.* -group \"%val{hook_param_capture_1}-trim-indent\"  html-trim-indent\n    hook window InsertChar '>' -group \"%val{hook_param_capture_1}-indent\" html-indent-on-greater-than\n    hook window InsertChar \\n -group \"%val{hook_param_capture_1}-indent\" html-indent-on-new-line\n\n    hook -once -always window WinSetOption \"filetype=.*\" \"\n        remove-hooks window \"\"%val{hook_param_capture_1}-.+\"\"\n    \"\n}\n\nhook -group html-highlight global WinSetOption filetype=(html|xml) %{\n    add-highlighter \"window/%val{hook_param_capture_1}\" ref html\n    hook -once -always window WinSetOption \"filetype=.*\" \"\n        remove-highlighter \"\"window/%val{hook_param_capture_1}\"\"\n    \"\n}\n\n\nprovide-module html %[\n\ntry %{\n    require-module css\n    require-module javascript\n}\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/html regions\nadd-highlighter shared/html/comment region <!--     -->                  fill comment\nadd-highlighter shared/html/tag     region <          >                  regions\nadd-highlighter shared/html/style   region <style\\b.*?>\\K  (?=</style>)  ref css\nadd-highlighter shared/html/script  region <script\\b.*?>\\K (?=</script>) ref javascript\n\nadd-highlighter shared/html/tag/base default-region group\nadd-highlighter shared/html/tag/ region '\"' (?<!\\\\)(\\\\\\\\)*\"      fill string\nadd-highlighter shared/html/tag/ region \"'\" \"'\"                  fill string\n\nadd-highlighter shared/html/tag/base/ regex \\b([a-zA-Z0-9_-]+)=? 1:attribute\nadd-highlighter shared/html/tag/base/ regex </?(\\w+) 1:keyword\nadd-highlighter shared/html/tag/base/ regex <(!DOCTYPE(\\h+\\w+)+) 1:meta\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden html-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden html-indent-on-greater-than %[\n    evaluate-commands -draft -itersel %[\n        # align closing tag to opening when alone on a line\n        try %[ execute-keys -draft , <a-h> s ^\\h+<lt>/(\\w+)<gt>$ <ret> {c<lt><c-r>1,<lt>/<c-r>1<gt> <ret> s \\A|.\\z <ret> 1<a-&> ]\n    ]\n]\n\ndefine-command -hidden html-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : html-trim-indent <ret> }\n        # indent after lines ending with opening tag except when it starts with a closing tag\n        try %{ execute-keys -draft k x <a-k> <lt>(?!area)(?!base)(?!br)(?!col)(?!command)(?!embed)(?!hr)(?!img)(?!input)(?!keygen)(?!link)(?!menuitem)(?!meta)(?!param)(?!source)(?!track)(?!wbr)(?!/)(?!>)[a-zA-Z0-9_-]+[^>]*?>$ <ret>jx<a-K>^\\s*<lt>/<ret><a-gt> } }\n}\n\n]\n"
  },
  {
    "path": "rc/filetype/hyprlang.kak",
    "content": "# https://hypr.land\n\nhook global BufCreate .*/hypr/.*[.]conf %{\n    set-option buffer filetype hyprlang\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=hyprlang %{\n    require-module hyprlang\n\n    hook window ModeChange pop:insert:.* -group hyprlang-trim-indent hyprlang-trim-indent\n    hook window InsertChar .* -group hyprlang-indent hyprlang-indent-on-char\n    hook window InsertChar \\n -group hyprlang-indent hyprlang-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window hyprlang-.+ }\n}\n\nhook -group hyprlang-highlight global WinSetOption filetype=hyprlang %{\n    add-highlighter window/hyprlang ref hyprlang\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/hyprlang }\n}\n\nprovide-module hyprlang %@\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/hyprlang regions\nadd-highlighter shared/hyprlang/code default-region group\n\nadd-highlighter shared/hyprlang/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\n\nadd-highlighter shared/hyprlang/line_comment region '#' $ fill comment\n\nadd-highlighter shared/hyprlang/code/variable regex ((?<![-:])\\b\\w+)\\s*= 1:variable\nadd-highlighter shared/hyprlang/code/dollarvar regex (\\$\\w+)\\b 1:value\n\nadd-highlighter shared/hyprlang/code/builtin regex \\b(true|false)\\b 0:value\nadd-highlighter shared/hyprlang/code/binary regex \\b(0b[01_]+)\\b 0:value\nadd-highlighter shared/hyprlang/code/octal regex \\b(0o[0-7_]+)\\b 0:value\nadd-highlighter shared/hyprlang/code/hex regex \\b(0x[a-fA-F0-9_]+)\\b 0:value\nadd-highlighter shared/hyprlang/code/decimal regex \\b([0-9-+][0-9_]*)\\b 0:value\nadd-highlighter shared/hyprlang/code/float regex \\b([0-9-+][0-9_]*\\.[0-9_]+)\\b 0:value\nadd-highlighter shared/hyprlang/code/float_exp regex \\b([0-9-+][0-9_]*(\\.[0-9_]+)?[eE][-+]?[0-9_]+)\\b 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden hyprlang-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden hyprlang-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m <a-S> 1<a-&> >\n    >\n>\n\ndefine-command -hidden hyprlang-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : hyprlang-trim-indent <ret> }\n        # indent after lines ending with opener token\n        try %< execute-keys -draft k x <a-k> [[{]\\h*$ <ret> j <a-gt> >\n        # deindent closer token(s) when after cursor\n        try %< execute-keys -draft x <a-k> ^\\h*[}\\]] <ret> gh / [}\\]] <ret> m <a-S> 1<a-&> >\n    >\n>\n\n@\n"
  },
  {
    "path": "rc/filetype/i3.kak",
    "content": "hook global BufCreate .*(sway|i3)/config %{\n    set buffer filetype i3\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=i3 %[\n    require-module i3\n\n    # cleanup trailing whitespaces when exiting insert mode\n    hook window ModeChange pop:insert:.* -group i3-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n    hook window InsertChar \\n -group i3-insert i3-insert-on-new-line\n    hook window InsertChar \\n -group i3-indent i3-indent-on-new-line\n    hook window InsertChar \\} -group i3-indent i3-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window i3-.+ }\n]\n\nhook -group i3-highlight global WinSetOption filetype=i3 %{\n    add-highlighter window/i3 ref i3\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/i3 }\n}\n\n\nprovide-module i3 %[\n\nadd-highlighter shared/i3 regions\nadd-highlighter shared/i3/code default-region group\nadd-highlighter shared/i3/double_string region %{\"} %{\"} group\nadd-highlighter shared/i3/single_string region %{'} %{'} group\nadd-highlighter shared/i3/exec region %{((?<=exec )|(?<=--no-startup-id ))(?!--no-startup-id)} \"$\" fill string\nadd-highlighter shared/i3/comment region \"#\" \"$\" fill comment\n\nadd-highlighter shared/i3/double_string/ fill string\nadd-highlighter shared/i3/single_string/ fill string\n\n# Symbols\nadd-highlighter shared/i3/code/ regex \"[+|→]\" 0:operator\nadd-highlighter shared/i3/code/ regex \"\\$\\w+\" 0:variable\n\n# keys\nadd-highlighter shared/i3/code/ regex \"\\b(Shift|Control|Ctrl|Mod1|Mod2|Mod3|Mod4|Mod5|Mode_switch|Return|Escape|Print)\\b\" 0:value\n\n# keywords\nadd-highlighter shared/i3/code/ regex \"\\b(bind|bindcode|bindsym|assign|new_window|default_(floating_)?border|popup_during_fullscreen|font|floating_modifier|default_orientation|workspace_layout|for_window|focus_follows_mouse|bar|position|colors|output|tray_output|workspace_buttons|workspace_auto_back_and_forth|binding_mode_indicator|debuglog|floating_minimum_size|floating_maximum_size|force_focus_wrapping|force_xinerama|force_display_urgency_hint|hidden_state|modifier|new_float|shmlog|socket_path|verbose|mouse_warping|strip_workspace_numbers|focus_on_window_activation|no_focus|set|mode|set_from_resource)\\b\" 0:keyword\n# function keywords\nadd-highlighter shared/i3/code/ regex \"\\b(exit|reload|restart|kill|fullscreen|global|layout|border|focus|move|open|split|append_layout|mark|unmark|resize|grow|shrink|show|nop|rename|title_format|sticky)\\b\" 0:function\nadd-highlighter shared/i3/code/ regex \"\\b(exec|exec_always|i3bar_command|status_command)\\b\" 0:function\n# \" these are not keywords but we add them for consistency\nadd-highlighter shared/i3/code/ regex \"\\b(no|false|inactive)\\b\" 0:value\n\n# values\nadd-highlighter shared/i3/code/ regex \"\\b(1pixel|default|stacked|tabbed|normal|none|tiling|stacking|floating|enable|disable|up|down|horizontal|vertical|auto|up|down|left|right|parent|child|px|or|ppt|leave_fullscreen|toggle|mode_toggle|scratchpad|width|height|top|bottom|client|hide|primary|yes|all|active|window|container|to|absolute|center|on|off|ms|smart|ignore|pixel|splith|splitv|output|true)\\b\" 0:value\nadd-highlighter shared/i3/code/ regex \"\\b(next|prev|next_on_output|prev_on_output|back_and_forth|current|number|none|vertical|horizontal|both|dock|hide|invisible|gaps|smart_gaps|smart_borders|inner|outer|current|all|plus|minus|no_gaps)\\b\" 0:value\n\n# double-dash arguments\nadd-highlighter shared/i3/code/ regex \"--(release|border|whole-window|toggle|no-startup-id)\" 0:attribute\n\n# color\nadd-highlighter shared/i3/double_string/ regex \"#[0-9a-fA-F]{6}\" 0:value\nadd-highlighter shared/i3/single_string/ regex \"#[0-9a-fA-F]{6}\" 0:value\n\n# attributes\nadd-highlighter shared/i3/code/ regex \"client\\.(background|statusline|background|separator|statusline)\" 1:attribute\nadd-highlighter shared/i3/code/ regex \"client\\.(focused_inactive|focused_tab_title|focused|unfocused|urgent|inactive_workspace|urgent_workspace|focused_workspace|active_workspace|placeholder)\" 1:attribute\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden i3-insert-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # copy # comments prefix\n        try %{ execute-keys -draft kx s ^\\h*#\\h* <ret> y jgh P }\n    =\n~\n\ndefine-command -hidden i3-indent-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # indent after lines ending with {\n        try %[ execute-keys -draft kx <a-k> \\{\\h*$ <ret> j<a-gt> ]\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n    =\n~\n\ndefine-command -hidden i3-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\n]\n"
  },
  {
    "path": "rc/filetype/ini.kak",
    "content": "hook global BufCreate .+\\.ini %{\n    set-option buffer filetype ini\n}\n\nhook global WinSetOption filetype=ini %{\n    require-module ini\n}\n\nhook -group ini-highlight global WinSetOption filetype=ini %{\n    add-highlighter window/ini ref ini\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/ini }\n}\n\n\nprovide-module ini %{\n\nadd-highlighter shared/ini regions\nadd-highlighter shared/ini/code default-region group\nadd-highlighter shared/ini/comment region '(^|\\h)\\K[#;]' $ fill comment\n\nadd-highlighter shared/ini/code/ regex \"(?S)^\\h*(\\[.+?\\])\\h*$\" 1:title\nadd-highlighter shared/ini/code/ regex \"^\\h*([^\\[][^=\\n]*)=([^\\n]*)\" 1:variable 2:value\n\n}\n"
  },
  {
    "path": "rc/filetype/janet.kak",
    "content": "# http://janet-lang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](janet|jdn) %{\n    set-option buffer filetype janet\n}\n\n\nhook global WinSetOption filetype=janet %{\n    require-module janet\n\n    hook window ModeChange pop:insert:.* -group janet-trim-indent janet-trim-indent\n    hook window InsertChar \\n -group janet-indent janet-indent-on-new-line\n    set-option buffer extra_word_chars ! @ $ '%' ^ & * - _ + = : < > . ?\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window janet-.+ }\n}\n\nhook -group janet-highlight global WinSetOption filetype=janet %{\n    add-highlighter window/janet ref janet\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/janet }\n}\n\nprovide-module janet %{\n\nrequire-module lisp\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/janet regions\nadd-highlighter shared/janet/code default-region group\nadd-highlighter shared/janet/comment region '(?<!\\\\)(?:\\\\\\\\)*\\K#' '$' fill comment\nadd-highlighter shared/janet/comment-form region -recurse \\( '(?<!\\\\)(?:\\\\\\\\)*\\K\\(comment ' '\\)' fill comment\nadd-highlighter shared/janet/string region '(?<!\\\\)(?:\\\\\\\\)*\\K\"' '(?<!\\\\)(?:\\\\\\\\)*\"' fill string\nadd-highlighter shared/janet/raw-string region -match-capture '(`+)' '(`+)' fill string\nadd-highlighter shared/janet/code/ regex \\b(nil|true|false)\\b 0:value\nadd-highlighter shared/janet/code/number regex \\W\\K(?:[\\-+]?\\dx?[\\der._+a-f]*)\\b 0:value\nadd-highlighter shared/janet/code/function-definition regex \\((?:defn-?|fn)\\s([!@$%\\^&*\\-_+=:<>.?\\w/]+) 1:function\nadd-highlighter shared/janet/code/function-call regex \\(([!@$%\\^&*\\-_+=:<>.?\\w/]+) 1:function\nadd-highlighter shared/janet/code/keyword regex \\W\\K:[!@$%\\^&*\\-_+=:<>.?\\w/]+ 0:value\nadd-highlighter shared/janet/code/special regex \\((def-?|defn-?|var-?|fn|do|quote|if|splice|while|break|set|quasiquote|unquote|upscope)\\s 1:keyword\nadd-highlighter shared/janet/code/ regex \\W\\K(&|&opt|&keys|&named)\\W 1:keyword\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden janet-trim-indent lisp-trim-indent\n\ndeclare-option \\\n    -docstring 'regex matching the head of forms which have options *and* indented bodies' \\\n    regex janet_special_indent_forms \\\n    '(?:def.*|while|for|fn\\*?|if(-.*|)|let.*|loop|seq|with(-.*|)|when(-.*|))|defer|do|match|var'\n\ndefine-command -hidden janet-indent-on-new-line %{\n    # registers: i = best align point so far; w = start of first word of form\n    evaluate-commands -draft -save-regs '/\"|^@iw' -itersel %{\n        execute-keys -draft 'gk\"iZ'\n        try %{\n            execute-keys -draft '[bl\"i<a-Z><gt>\"wZ'\n\n            try %{\n                # If a special form, indent another j\n                execute-keys -draft '\"wze<a-k>\\A' %opt{janet_special_indent_forms} '\\z<ret><a-L>s.\\K.*<ret><a-;>;\"i<a-Z><gt>'\n            } catch %{\n                # If not special and parameter appears on line 1, indent to parameter\n                execute-keys -draft '\"wze<a-l>s\\h\\K[^\\s].*<ret><a-;>;\"i<a-Z><gt>'\n            }\n        }\n        try %{ execute-keys -draft '[rl\"i<a-Z><gt>' }\n        try %{ execute-keys -draft '[Bl\"i<a-Z><gt>' }\n        execute-keys -draft '\"i<a-z>a&,'\n        # trim trailing whitespace on the previous line\n        try %{ execute-keys -draft k : janet-trim-indent <ret> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/java.kak",
    "content": "hook global BufCreate .*\\.java %{\n    set-option buffer filetype java\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=java %{\n    require-module java\n\n    set-option window static_words %opt{java_static_words}\n\n    # cleanup trailing whitespaces when exiting insert mode\n    hook window ModeChange pop:insert:.* -group java-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n    hook window InsertChar \\n -group java-insert java-insert-on-new-line\n    hook window InsertChar \\n -group java-indent java-indent-on-new-line\n    hook window InsertChar \\{ -group java-indent java-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group java-indent java-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window java-.+ }\n}\n\nhook -group java-highlight global WinSetOption filetype=java %{\n    add-highlighter window/java ref java\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/java }\n}\n\nprovide-module java %§\n\nadd-highlighter shared/java regions\nadd-highlighter shared/java/code default-region group\nadd-highlighter shared/java/string region %{(?<!')\"} %{(?<!\\\\)(\\\\\\\\)*\"} fill string\nadd-highlighter shared/java/character region %{'} %{(?<!\\\\)'} fill value\nadd-highlighter shared/java/comment region /\\* \\*/ fill comment\nadd-highlighter shared/java/inline_documentation region /// $ fill documentation\nadd-highlighter shared/java/line_comment region // $ fill comment\n\nadd-highlighter shared/java/code/ regex \"(?<!\\w)@\\w+\\b\" 0:meta\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden java-insert-on-new-line %[\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K/{2,}\\h* <ret> y<c-o>P<esc> }\n]\n\ndefine-command -hidden java-indent-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # indent after lines ending with { or (\n        try %[ execute-keys -draft kx <a-k> [{(]\\h*$ <ret> j<a-gt> ]\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        # align to opening paren of previous line\n        try %{ execute-keys -draft [( <a-k> \\A\\([^\\n]+\\n[^\\n]*\\n?\\z <ret> s \\A\\(\\h*.|.\\z <ret> '<a-;>' & }\n        # indent after a switch's case/default statements\n        try %[ execute-keys -draft kx <a-k> ^\\h*(case|default).*:$ <ret> j<a-gt> ]\n        # indent after keywords\n        try %[ execute-keys -draft <semicolon><a-F>)MB <a-k> \\A(if|else|while|for|try|catch)\\h*\\(.*\\)\\h*\\n\\h*\\n?\\z <ret> s \\A|.\\z <ret> 1<a-&>1<a-,><a-gt> ]\n        # deindent closing brace(s) when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> ]\n    =\n~\n\ndefine-command -hidden java-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden java-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\n# Shell\n# ‾‾‾‾‾\n# Oracle 2021, 3.9 Keywords, Chapter 3. Lexical Structure, Java Language Specification, Java SE 17, viewed 25 September 2021, <https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.9>\n#\nevaluate-commands %sh{\n    values='false null this true'\n\n    types='boolean byte char double float int long short unsigned void'\n\n    keywords='assert break case catch class continue default do else enum extends\n        finally for if implements import instanceof interface new package return\n        static strictfp super switch throw throws try var while yield'\n\n    attributes='abstract final native non-sealed permits private protected public\n        record sealed synchronized transient volatile'\n\n    modules='exports module open opens provides requires to transitive uses with'\n\n    # ---------------------------------------------------------------------------------------------- #\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n    # ---------------------------------------------------------------------------------------------- #\n    add_highlighter() { printf \"add-highlighter shared/java/code/ regex %s %s\\n\" \"$1\" \"$2\"; }\n    # ---------------------------------------------------------------------------------------------- #\n    add_word_highlighter() {\n\n      while [ $# -gt 0 ]; do\n          words=$1 face=$2; shift 2\n          regex=\"\\\\b($(join \"${words}\" '|'))\\\\b\"\n          add_highlighter \"$regex\" \"1:$face\"\n      done\n\n    }\n\n    # highlight: open<space> not open()\n    add_module_highlighter() {\n\n      while [ $# -gt 0 ]; do\n          words=$1 face=$2; shift 2\n          regex=\"\\\\b($(join \"${words}\" '|'))\\\\b(?=\\\\s)\"\n          add_highlighter \"$regex\" \"1:$face\"\n      done\n\n    }\n    # ---------------------------------------------------------------------------------------------- #\n    printf %s\\\\n \"declare-option str-list java_static_words $(join \"${values} ${types} ${keywords} ${attributes} ${modules}\" ' ')\"\n    # ---------------------------------------------------------------------------------------------- #\n    add_word_highlighter \"$values\" \"value\" \"$types\" \"type\" \"$keywords\" \"keyword\" \"$attributes\" \"attribute\"\n    # ---------------------------------------------------------------------------------------------- #\n    add_module_highlighter \"$modules\" \"module\"\n    # ---------------------------------------------------------------------------------------------- #\n}\n\n§\n"
  },
  {
    "path": "rc/filetype/javascript.kak",
    "content": "# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.][cm]?(js)x? %{\n    set-option buffer filetype javascript\n}\n\nhook global BufCreate .*[.][cm]?(ts)x? %{\n    set-option buffer filetype typescript\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=(javascript|typescript) %{\n    require-module javascript\n\n    hook window ModeChange pop:insert:.* -group \"%val{hook_param_capture_1}-trim-indent\" javascript-trim-indent\n    hook window InsertChar .* -group \"%val{hook_param_capture_1}-indent\" javascript-indent-on-char\n    hook window InsertChar \\n -group \"%val{hook_param_capture_1}-insert\" javascript-insert-on-new-line\n    hook window InsertChar \\n -group \"%val{hook_param_capture_1}-indent\" javascript-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* \"\n        remove-hooks window %val{hook_param_capture_1}-.+\n    \"\n}\n\nhook -group javascript-highlight global WinSetOption filetype=javascript %{\n    add-highlighter window/javascript ref javascript\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/javascript }\n}\n\nhook -group typescript-highlight global WinSetOption filetype=typescript %{\n    add-highlighter window/typescript ref typescript\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/typescript }\n}\n\n\nprovide-module javascript %§\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden javascript-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft x 1s^(\\h+)$<ret> d }\n}\n\ndefine-command -hidden javascript-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %/ execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m s \\A|.\\z <ret> 1<a-&> /\n    >\n>\n\ndefine-command -hidden javascript-insert-on-new-line %<\n    evaluate-commands -draft -itersel %<\n    execute-keys <semicolon>\n    try %[\n        evaluate-commands -draft -save-regs '/\"' %[\n            # copy the commenting prefix\n            execute-keys -save-regs '' k x1s^\\h*(//+\\h*)<ret> y\n            try %[\n                # if the previous comment isn't empty, create a new one\n                execute-keys x<a-K>^\\h*//+\\h*$<ret> jxs^\\h*<ret>P\n            ] catch %[\n                # if there is no text in the previous comment, remove it completely\n                execute-keys d\n            ]\n        ]\n    ]\n    try %[\n        # if the previous line isn't within a comment scope, break\n        execute-keys -draft kx <a-k>^(\\h*/\\*|\\h+\\*(?!/))<ret>\n\n        # find comment opening, validate it was not closed, and check its using star prefixes\n        execute-keys -draft <a-?>/\\*<ret><a-H> <a-K>\\*/<ret> <a-k>\\A\\h*/\\*([^\\n]*\\n\\h*\\*)*[^\\n]*\\n\\h*.\\z<ret>\n\n        try %[\n            # if the previous line is opening the comment, insert star preceeded by space\n            execute-keys -draft kx<a-k>^\\h*/\\*<ret>\n            execute-keys -draft i*<space><esc>\n        ] catch %[\n           try %[\n                # if the next line is a comment line insert a star\n                execute-keys -draft jx<a-k>^\\h+\\*<ret>\n                execute-keys -draft i*<space><esc>\n            ] catch %[\n                try %[\n                    # if the previous line is an empty comment line, close the comment scope\n                    execute-keys -draft kx<a-k>^\\h+\\*\\h+$<ret> x1s\\*(\\h*)<ret>c/<esc>\n                ] catch %[\n                    # if the previous line is a non-empty comment line, add a star\n                    execute-keys -draft i*<space><esc>\n                ]\n            ]\n        ]\n\n        # trim trailing whitespace on the previous line\n        try %[ execute-keys -draft s\\h+$<ret> d ]\n        # align the new star with the previous one\n        execute-keys Kx1s^[^*]*(\\*)<ret><a-(><a-&>\n    ]\n    >\n>\n\ndefine-command -hidden javascript-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n    execute-keys <semicolon>\n    try %<\n        # if previous line is part of a comment, do nothing\n        execute-keys -draft <a-?>/\\*<ret> <a-K>^\\h*[^/*\\h]<ret>\n    > catch %<\n        # else if previous line closed a paren (possibly followed by words and a comment),\n        # copy indent of the opening paren line\n        execute-keys -draft kx 1s(\\))(\\h+\\w+)*\\h*(\\;\\h*)?(?://[^\\n]+)?\\n\\z<ret> m<a-semicolon>J <a-S> 1<a-&>\n    > catch %<\n        # else indent new lines with the same level as the previous one\n        execute-keys -draft K <a-&>\n    >\n    # remove previous empty lines resulting from the automatic indent\n    try %< execute-keys -draft k x <a-k>^\\h+$<ret> Hd >\n    # indent after an opening brace or parenthesis at end of line\n    try %< execute-keys -draft k x <a-k>[{(]\\h*$<ret> j <a-gt> >\n    # indent after a label (works for case statements)\n    try %< execute-keys -draft k x s[a-zA-Z0-9_-]+:\\h*$<ret> j <a-gt> >\n    # indent after a statement not followed by an opening brace\n    try %< execute-keys -draft k x s\\)\\h*(?://[^\\n]+)?\\n\\z<ret> \\\n                               <a-semicolon>mB <a-k>\\A\\b(if|for|while)\\b<ret> <a-semicolon>j <a-gt> >\n    try %< execute-keys -draft k x s \\belse\\b\\h*(?://[^\\n]+)?\\n\\z<ret> \\\n                               j <a-gt> >\n    # deindent after a single line statement end\n    try %< execute-keys -draft K x <a-k>\\;\\h*(//[^\\n]+)?$<ret> \\\n                               K x s\\)(\\h+\\w+)*\\h*(//[^\\n]+)?\\n([^\\n]*\\n){2}\\z<ret> \\\n                               MB <a-k>\\A\\b(if|for|while)\\b<ret> <a-S>1<a-&> >\n    try %< execute-keys -draft K x <a-k>\\;\\h*(//[^\\n]+)?$<ret> \\\n                               K x s \\belse\\b\\h*(?://[^\\n]+)?\\n([^\\n]*\\n){2}\\z<ret> \\\n                               <a-S>1<a-&> >\n    # deindent closing brace(s) when after cursor\n    try %< execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <esc> m <a-S> 1<a-&> >\n    # align to the opening parenthesis or opening brace (whichever is first)\n    # on a previous line if its followed by text on the same line\n    try %< evaluate-commands -draft %<\n        # Go to opening parenthesis and opening brace, then select the most nested one\n        try %< execute-keys [c [({],[)}] <ret> >\n        # Validate selection and get first and last char\n        execute-keys <a-k>\\A[{(](\\h*\\S+)+\\n<ret> <a-K>\"(([^\"]*\"){2})*<ret> <a-K>'(([^']*'){2})*<ret> <a-:><a-semicolon>L <a-S>\n        # Remove possibly incorrect indent from new line which was copied from previous line\n        try %< execute-keys -draft , <a-h> s\\h+<ret> d >\n        # Now indent and align that new line with the opening parenthesis/brace\n        execute-keys 1<a-&> &\n     > >\n    >\n>\n\n# Highlighting and hooks bulder for JavaScript and TypeScript\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\ndefine-command -hidden init-javascript-filetype -params 1 %~\n    # Highlighters\n    # ‾‾‾‾‾‾‾‾‾‾‾‾\n\n    add-highlighter \"shared/%arg{1}\" regions\n    add-highlighter \"shared/%arg{1}/code\" default-region group\n    add-highlighter \"shared/%arg{1}/double_string\" region '\"'  (?<!\\\\)(\\\\\\\\)*\"         fill string\n    add-highlighter \"shared/%arg{1}/single_string\" region \"'\"  (?<!\\\\)(\\\\\\\\)*'         fill string\n    add-highlighter \"shared/%arg{1}/literal\"       region \"`\"  (?<!\\\\)(\\\\\\\\)*`         group\n    add-highlighter \"shared/%arg{1}/comment_line\"  region //   '$'                     fill comment\n    add-highlighter \"shared/%arg{1}/comment\"       region /\\*  \\*/                     fill comment\n    add-highlighter \"shared/%arg{1}/shebang\"       region ^#!  $                       fill meta\n    add-highlighter \"shared/%arg{1}/division\" region '[\\w\\)\\]]\\K(/|(\\h+/\\s+))' '(?=\\w)' group # Help Kakoune to better detect /…/ literals\n    add-highlighter \"shared/%arg{1}/regex\"         region /    (?<!\\\\)(\\\\\\\\)*/[gimuy]* fill meta\n    add-highlighter \"shared/%arg{1}/jsx\"           region -recurse (?<![\\w<])<[\\w>][\\w:.-]* (?<![\\w<])<[\\w>][\\w:.-]*(?!\\hextends)(?=[\\s/>])(?!>\\()) (</.*?>|/>) regions\n\n    # Regular expression flags are: g → global match, i → ignore case, m → multi-lines, u → unicode, y → sticky\n    # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp\n\n    add-highlighter \"shared/%arg{1}/literal/\"       fill string\n    add-highlighter \"shared/%arg{1}/literal/\"       regex \\$\\{.*?\\} 0:value\n\n    add-highlighter \"shared/%arg{1}/code/\" regex (?:^|[^$_])\\b(document|false|null|parent|self|this|true|undefined|window)\\b 1:value\n    add-highlighter \"shared/%arg{1}/code/\" regex \"-?\\b[0-9]*\\.?[0-9]+\" 0:value\n    add-highlighter \"shared/%arg{1}/code/\" regex \\b(Array|Boolean|Date|Function|Number|Object|RegExp|String|Symbol)\\b 0:type\n\n    # jsx: In well-formed xml the number of opening and closing tags match up regardless of tag name.\n    #\n    # We inline a small XML highlighter here since it anyway need to recurse back up to the starting highlighter.\n    # To make things simple we assume that jsx is always enabled.\n\n    add-highlighter \"shared/%arg{1}/jsx/tag\"  region -recurse <  <(?=[/\\w>]) (?<!=)> regions\n    add-highlighter \"shared/%arg{1}/jsx/expr\" region -recurse \\{ \\{             \\}      ref %arg{1}\n\n    add-highlighter \"shared/%arg{1}/jsx/tag/base\" default-region group\n    add-highlighter \"shared/%arg{1}/jsx/tag/double_string\" region =\\K\" (?<!\\\\)(\\\\\\\\)*\" fill string\n    add-highlighter \"shared/%arg{1}/jsx/tag/single_string\" region =\\K' (?<!\\\\)(\\\\\\\\)*' fill string\n    add-highlighter \"shared/%arg{1}/jsx/tag/expr\" region -recurse \\{ \\{   \\}           group\n\n    add-highlighter \"shared/%arg{1}/jsx/tag/base/\" regex (\\w+) 1:attribute\n    add-highlighter \"shared/%arg{1}/jsx/tag/base/\" regex </?([\\w-$]+) 1:keyword\n    add-highlighter \"shared/%arg{1}/jsx/tag/base/\" regex (</?|/?>) 0:meta\n\n    add-highlighter \"shared/%arg{1}/jsx/tag/expr/\"   ref %arg{1}\n\n    # Keywords are collected at\n    # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords\n    # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get\n    # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set\n    add-highlighter \"shared/%arg{1}/code/\" regex \\b(async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|export|extends|finally|for|function|get|if|import|in|instanceof|let|new|of|return|set|static|super|switch|throw|try|typeof|var|void|while|with|yield)\\b 0:keyword\n~\n\ninit-javascript-filetype javascript\ninit-javascript-filetype typescript\n\n# Highlighting specific to TypeScript\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\nadd-highlighter shared/typescript/code/ regex \\b(array|boolean|date|number|object|regexp|string|symbol)\\b 0:type\n\n# Keywords grabbed from https://github.com/Microsoft/TypeScript/issues/2536\nadd-highlighter shared/typescript/code/ regex \\b(as|constructor|declare|enum|from|implements|interface|module|namespace|package|private|protected|public|readonly|static|type)\\b 0:keyword\n\n§\n\n# Aliases\n# ‾‾‾‾‾‾‾\nprovide-module typescript %{ require-module javascript }\n"
  },
  {
    "path": "rc/filetype/jinja.kak",
    "content": "# https://palletsprojects.com/p/jinja/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nprovide-module jinja %[\n\nrequire-module python\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/jinja regions\nadd-highlighter shared/jinja/comment region '\\{#' '#\\}' fill comment\n\n# TODO: line statements # …\n\nadd-highlighter shared/jinja/statement region '\\{%' '%\\}' group\nadd-highlighter shared/jinja/statement/ ref python\nadd-highlighter shared/jinja/statement/ regex \\{%[+-]?|[+-]?%\\} 0:value\nadd-highlighter shared/jinja/statement/tests regex \\b(callable|even|le|none|string|defined|ge|lower|number|undefined|divisibleby|gt|lt|odd|upper|eq|in|mapping|sameas|escaped|iterable|ne|sequence)\\b 0:builtin\nadd-highlighter shared/jinja/statement/functions regex \\b(range|lipsum)\\b 0:function\nadd-highlighter shared/jinja/statement/macro regex \\b(((end)?(call|macro)))\\b 0:keyword\nadd-highlighter shared/jinja/statement/extensions regex \\b(((end)?(block|trans))|(pluralize))\\b 0:keyword\nadd-highlighter shared/jinja/statement/control regex \\b(((end)?(if|for|with))|(break|continue))\\b 0:keyword\nadd-highlighter shared/jinja/statement/filters regex \\b(?:(?:(filter)\\s+|\\|\\s*)(abs|attr|batch|capitalize|center|default|dictsort|e|escape|filesizeformat|first|float|forceescape|format|groupby|indent|int|join|last|length|list|lower|map|max|min|pprint|random|reject|rejectattr|replace|reverse|round|safe|select|selectattr|slice|sort|string|striptags|sum|title|tojson|trim|truncate|unique|upper|urlencode|urlize|wordcount|wordwrap|xmlattr)|(endfilter))\\b 1:keyword 3:keyword 2:builtin\nadd-highlighter shared/jinja/statement/ regex \\b((end)?(autoescape|raw|set))\\b 0:keyword\nadd-highlighter shared/jinja/statement/ regex \\b(do|extends|include)\\b 0:keyword\nadd-highlighter shared/jinja/statement/ regex \\bignore\\s+missing\\b 0:meta\nadd-highlighter shared/jinja/statement/ regex \\bwith(out)?\\s+context\\b 0:meta\n\nadd-highlighter shared/jinja/expression region \\{\\{(?!\\{) (?<!\\})\\}\\} group\nadd-highlighter shared/jinja/expression/ ref python\nadd-highlighter shared/jinja/expression/ regex \\{\\{(?!\\{)|(?<!\\})\\}\\} 0:value\nadd-highlighter shared/jinja/expression/filters regex \\|\\s*(abs|attr|batch|capitalize|center|default|dictsort|e|escape|filesizeformat|first|float|forceescape|format|groupby|indent|int|join|last|length|list|lower|map|max|min|pprint|random|reject|rejectattr|replace|reverse|round|safe|select|selectattr|slice|sort|string|striptags|sum|title|tojson|trim|truncate|unique|upper|urlencode|urlize|wordcount|wordwrap|xmlattr)\\b 1:builtin\n\n]\n"
  },
  {
    "path": "rc/filetype/json.kak",
    "content": "# http://json.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](json) %{\n    set-option buffer filetype json\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=json %{\n    require-module json\n\n    hook window ModeChange pop:insert:.* -group json-trim-indent json-trim-indent\n    hook window InsertChar .* -group json-indent json-indent-on-char\n    hook window InsertChar \\n -group json-indent json-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window json-.+ }\n}\n\nhook -group json-highlight global WinSetOption filetype=json %{\n    add-highlighter window/json ref json\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/json }\n}\n\n\nprovide-module json %(\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/json regions\nadd-highlighter shared/json/code default-region group\nadd-highlighter shared/json/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\n\nadd-highlighter shared/json/code/ regex \\b(true|false|null|\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d*)?)\\b 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden json-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden json-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m <a-S> 1<a-&> >\n    >\n>\n\ndefine-command -hidden json-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : json-trim-indent <ret> }\n        # indent after lines ending with opener token\n        try %< execute-keys -draft k x <a-k> [[{]\\h*$ <ret> j <a-gt> >\n        # deindent closer token(s) when after cursor\n        try %< execute-keys -draft x <a-k> ^\\h*[}\\]] <ret> gh / [}\\]] <ret> m <a-S> 1<a-&> >\n    >\n>\n\n)\n"
  },
  {
    "path": "rc/filetype/json5.kak",
    "content": "# http://json5.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](json5) %{\n    set-option buffer filetype json5\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=json5 %{\n    require-module json5\n\n    hook window ModeChange pop:insert:.* -group json5-trim-indent json5-trim-indent\n    hook window InsertChar \\n -group json5-indent json5-indent-on-new-line\n    hook window InsertChar .* -group json5-indent json5-indent-on-char\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window json5-.+ }\n}\n\nhook -group json5-highlight global WinSetOption filetype=json5 %{\n    add-highlighter window/json5 ref json5\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/json5 }\n}\n\n\nprovide-module json5 %(\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/json5 regions\nadd-highlighter shared/json5/code default-region group\nadd-highlighter shared/json5/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/json5/string2 region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/json5/comment region '//' '$' fill comment\nadd-highlighter shared/json5/multiline_comment region /\\* \\*/ fill comment\n\nadd-highlighter shared/json5/code/ regex ^(\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\nadd-highlighter shared/json5/code/ regex ,(\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\nadd-highlighter shared/json5/code/ regex \\{(\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\n\nadd-highlighter shared/json5/code/ regex \\b(true|false|null|\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d*)?|0x[\\da-fA-F]+)\\b 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden json5-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden json5-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m <a-S> 1<a-&> >\n    >\n>\n\ndefine-command -hidden json5-indent-on-new-line %{\n    evaluate-commands -itersel -draft %{\n        execute-keys <semicolon>\n        try %{\n            evaluate-commands -draft -save-regs '/\"' %{\n                # copy the commenting prefix\n                execute-keys -save-regs '' k x1s^(\\h*//+\\h*)<ret> y\n                try %{\n                    # if the previous comment isn't empty, create a new one\n                    execute-keys x<a-K>^\\h*//+\\h*$<ret> jxs^\\h*<ret>P\n                } catch %{\n                    # if there is no text in the previous comment, remove it completely\n                    execute-keys d\n                }\n            }\n\n            # trim trailing whitespace on the previous line\n            try %{ execute-keys -draft k x s\\h+$<ret> d }\n        }\n\n        try %{\n            # if the previous line isn't within a comment scope, break\n            execute-keys -draft kx <a-k>^(\\h*/\\*|\\h+\\*(?!/))<ret>\n\n            # find comment opening, validate it was not closed, and check its using star prefixes\n            execute-keys -draft <a-?>/\\*<ret><a-H> <a-K>\\*/<ret> <a-k>\\A\\h*/\\*([^\\n]*\\n\\h*\\*)*[^\\n]*\\n\\h*.\\z<ret>\n\n            try %{\n                # if the previous line is opening the comment, insert star preceeded by space\n                execute-keys -draft kx<a-k>^\\h*/\\*<ret>\n                execute-keys -draft i*<space><esc>\n            } catch %{\n               try %{\n                    # if the next line is a comment line insert a star\n                    execute-keys -draft jx<a-k>^\\h+\\*<ret>\n                    execute-keys -draft i*<space><esc>\n                } catch %{\n                    try %{\n                        # if the previous line is an empty comment line, close the comment scope\n                        execute-keys -draft kx<a-k>^\\h+\\*\\h+$<ret> x1s\\*(\\h*)<ret>c/<esc>\n                    } catch %{\n                        # if the previous line is a non-empty comment line, add a star\n                        execute-keys -draft i*<space><esc>\n                    }\n                }\n            }\n\n            # trim trailing whitespace on the previous line\n            try %{ execute-keys -draft k x s\\h+$<ret> d }\n            # align the new star with the previous one\n            execute-keys Kx1s^[^*]*(\\*)<ret>&\n        }\n    }\n}\n\n)\n"
  },
  {
    "path": "rc/filetype/julia.kak",
    "content": "# http://julialang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.(jl) %{\n    set-option buffer filetype julia\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=julia %{\n    require-module julia\n    hook window InsertChar \\n -group julia-indent julia-indent-on-new-line\n    hook window InsertChar d -group julia-insert julia-insert-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window julia-.+ }\n}\n\nhook -group julia-highlight global WinSetOption filetype=julia %{\n    add-highlighter window/julia ref julia\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/julia }\n}\n\n\nprovide-module julia %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/julia regions\nadd-highlighter shared/julia/code default-region group\nadd-highlighter shared/julia/string  region '\"' (?<!\\\\)(\\\\\\\\)*\"         fill string\nadd-highlighter shared/julia/comment region -recurse '#=' '#=' '=#'     fill comment\nadd-highlighter shared/julia/line_comment region '#' '$'                fill comment\n\n# taken from https://github.com/JuliaEditorSupport/julia-emacs/blob/master/julia-mode.el\nadd-highlighter shared/julia/code/ regex %{\\b(true|false|C_NULL|Inf|NaN|Inf32|NaN32|nothing|\\b-?\\d+[fdiu]?)\\b} 0:value\nadd-highlighter shared/julia/code/ regex \\b(if|else|elseif|while|for|begin|end|quote|try|catch|return|local|abstract|function|macro|ccall|finally|typealias|break|continue|type|global|module|using|import|export|const|let|bitstype|do|in|baremodule|importall|immutable|mutable|struct|where)\\b 0:keyword\nadd-highlighter shared/julia/code/ regex \\b(Number|Real|BigInt|Integer|UInt|UInt8|UInt16|UInt32|UInt64|UInt128|Int|Int8|Int16|Int32|Int64|Int128|BigFloat|FloatingPoint|Float16|Float32|Float64|Complex128|Complex64|Bool|Cuchar|Cshort|Cushort|Cint|Cuint|Clonglong|Culonglong|Cintmax_t|Cuintmax_t|Cfloat|Cdouble|Cptrdiff_t|Cssize_t|Csize_t|Cchar|Clong|Culong|Cwchar_t|Cvoid|Char|ASCIIString|UTF8String|ByteString|SubString|AbstractString|Array|DArray|AbstractArray|AbstractVector|AbstractMatrix|AbstractSparseMatrix|SubArray|StridedArray|StridedVector|StridedMatrix|VecOrMat|StridedVecOrMat|DenseArray|SparseMatrixCSC|BitArray|Range|OrdinalRange|StepRange|UnitRange|FloatRange|Tuple|NTuple|Vararg|DataType|Symbol|Function|Vector|Matrix|Union|Type|Any|Complex|String|Ptr|Void|Exception|Task|Signed|Unsigned|Associative|Dict|IO|IOStream|Rational|Regex|RegexMatch|Set|IntSet|Expr|WeakRef|ObjectIdDict|AbstractRNG|MersenneTwister)\\b 0:type\nadd-highlighter shared/julia/code/ regex \\w+!*(?=\\() 0:function\nadd-highlighter shared/julia/code/ regex @\\w+!*\\b 0:meta\nadd-highlighter shared/julia/code/ regex '(?:\\?|=|:=|\\+=|-=|\\*=|/=|//=|\\.//=|\\.\\*=|\\./=|\\\\=|\\.\\\\=|\\^=|\\.\\^=|÷=|\\.÷=|%=|\\.%=|\\|=|&=|\\$=|=>|<<=|>>=|>>>=|~|\\.\\+=|\\.-=|--|-->|←|→|↔|↚|↛|↠|↣|↦|↮|⇎|⇏|⇒|⇔|⇴|⇶|⇷|⇸|⇹|⇺|⇻|⇼|⇽|⇾|⇿|⟵|⟶|⟷|⟷|⟹|⟺|⟻|⟼|⟽|⟾|⟿|⤀|⤁|⤂|⤃|⤄|⤅|⤆|⤇|⤌|⤍|⤎|⤏|⤐|⤑|⤔|⤕|⤖|⤗|⤘|⤝|⤞|⤟|⤠|⥄|⥅|⥆|⥇|⥈|⥊|⥋|⥎|⥐|⥒|⥓|⥖|⥗|⥚|⥛|⥞|⥟|⥢|⥤|⥦|⥧|⥨|⥩|⥪|⥫|⥬|⥭|⥰|⧴|⬱|⬰|⬲|⬳|⬴|⬵|⬶|⬷|⬸|⬹|⬺|⬻|⬼|⬽|⬾|⬿|⭀|⭁|⭂|⭃|⭄|⭇|⭈|⭉|⭊|⭋|⭌|￩|￫|&&|\\|\\||>|<|>=|≥|<=|≤|==|===|≡|!=|≠|!==|≢|\\.>|\\.<|\\.>=|\\.≥|\\.<=|\\.≤|\\.==|\\.!=|\\.≠|\\.=|\\.!|<:|>:|∈|∉|∋|∌|⊆|⊈|⊂|⊄|⊊|∝|∊|∍|∥|∦|∷|∺|∻|∽|∾|≁|≃|≄|≅|≆|≇|≈|≉|≊|≋|≌|≍|≎|≐|≑|≒|≓|≔|≕|≖|≗|≘|≙|≚|≛|≜|≝|≞|≟|≣|≦|≧|≨|≩|≪|≫|≬|≭|≮|≯|≰|≱|≲|≳|≴|≵|≶|≷|≸|≹|≺|≻|≼|≽|≾|≿|⊀|⊁|⊃|⊅|⊇|⊉|⊋|⊏|⊐|⊑|⊒|⊜|⊩|⊬|⊮|⊰|⊱|⊲|⊳|⊴|⊵|⊶|⊷|⋍|⋐|⋑|⋕|⋖|⋗|⋘|⋙|⋚|⋛|⋜|⋝|⋞|⋟|⋠|⋡|⋢|⋣|⋤|⋥|⋦|⋧|⋨|⋩|⋪|⋫|⋬|⋭|⋲|⋳|⋴|⋵|⋶|⋷|⋸|⋹|⋺|⋻|⋼|⋽|⋾|⋿|⟈|⟉|⟒|⦷|⧀|⧁|⧡|⧣|⧤|⧥|⩦|⩧|⩪|⩫|⩬|⩭|⩮|⩯|⩰|⩱|⩲|⩳|⩴|⩵|⩶|⩷|⩸|⩹|⩺|⩻|⩼|⩽|⩾|⩿|⪀|⪁|⪂|⪃|⪄|⪅|⪆|⪇|⪈|⪉|⪊|⪋|⪌|⪍|⪎|⪏|⪐|⪑|⪒|⪓|⪔|⪕|⪖|⪗|⪘|⪙|⪚|⪛|⪜|⪝|⪞|⪟|⪠|⪡|⪢|⪣|⪤|⪥|⪦|⪧|⪨|⪩|⪪|⪫|⪬|⪭|⪮|⪯|⪰|⪱|⪲|⪳|⪴|⪵|⪶|⪷|⪸|⪹|⪺|⪻|⪼|⪽|⪾|⪿|⫀|⫁|⫂|⫃|⫄|⫅|⫆|⫇|⫈|⫉|⫊|⫋|⫌|⫍|⫎|⫏|⫐|⫑|⫒|⫓|⫔|⫕|⫖|⫗|⫘|⫙|⫷|⫸|⫹|⫺|⊢|⊣|\\|>|<\\||:|\\.\\.|\\+|-|⊕|⊖|⊞|⊟|\\.\\+|\\.-|\\+\\+|\\||∪|∨|\\$|⊔|±|∓|∔|∸|≂|≏|⊎|⊻|⊽|⋎|⋓|⧺|⧻|⨈|⨢|⨣|⨤|⨥|⨦|⨧|⨨|⨩|⨪|⨫|⨬|⨭|⨮|⨹|⨺|⩁|⩂|⩅|⩊|⩌|⩏|⩐|⩒|⩔|⩖|⩗|⩛|⩝|⩡|⩢|⩣|<<|>>|>>>|\\.<<|\\.>>|\\.>>>|\\*|/|\\./|÷|\\.÷|%|⋅|∘|×|\\.%|\\.\\*|\\\\|\\.\\\\|&|∩|∧|⊗|⊘|⊙|⊚|⊛|⊠|⊡|⊓|∗|∙|∤|⅋|≀|⊼|⋄|⋆|⋇|⋉|⋊|⋋|⋌|⋏|⋒|⟑|⦸|⦼|⦾|⦿|⧶|⧷|⨇|⨰|⨱|⨲|⨳|⨴|⨵|⨶|⨷|⨸|⨻|⨼|⨽|⩀|⩃|⩄|⩋|⩍|⩎|⩑|⩓|⩕|⩘|⩚|⩜|⩞|⩟|⩠|⫛|⊍|▷|⨝|⟕|⟖|⟗|//|\\.//|\\^|\\.\\^|↑|↓|⇵|⟰|⟱|⤈|⤉|⤊|⤋|⤒|⤓|⥉|⥌|⥍|⥏|⥑|⥔|⥕|⥘|⥙|⥜|⥝|⥠|⥡|⥣|⥥|⥮|⥯|￪|￬|::|\\.)' 0:operator\n\ndefine-command -hidden julia-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # preserve previous line comment\n        try %{ execute-keys -draft k x <a-k> ^\\h*# <ret> j i <#> <space> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after block start\n        try %{ execute-keys -draft , k x <a-k> ^\\h*(struct|macro|function|begin|if|try|for|while|let|quote|do) <ret> <a-K> end$ <ret> j <a-gt> }\n        # deindent closing brace/bracket when after cursor (for arrays and dictionaries)\n        try %< execute-keys -draft x <a-k> ^\\h*[}\\]] <ret> gh / [}\\]] <ret> m <a-S> 1<a-&> >\n    >\n>\n\ndefine-command -hidden julia-insert-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # deindent on end\n        try %{ execute-keys -draft x <a-k> ^\\h*end <ret> <lt> }\n    >\n>\n§\n"
  },
  {
    "path": "rc/filetype/just.kak",
    "content": "# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*/?[jJ]ustfile %{\n    set-option buffer filetype justfile\n}\n\nhook global WinSetOption filetype=justfile %{\n    require-module justfile\n\n    hook window ModeChange pop:insert:.* -group justfile-trim-indent justfile-trim-indent\n    hook window InsertChar \\n -group justfile-insert just-insert-on-new-line\n    hook window InsertChar \\n -group justfile-indent just-indent-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window justfile-.+ }\n}\n\nhook -group justfile-highlight global WinSetOption filetype=justfile %{\n    add-highlighter window/justfile ref justfile\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/justfile }\n}\n\n\nprovide-module justfile %{\n\n# Indentation\n# ‾‾‾‾‾‾‾‾‾‾‾\n\ndefine-command -hidden justfile-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden just-insert-on-new-line %{\n    # copy '#' comment prefix and following white spaces\n    try %{ execute-keys -draft k x s ^\\h*//\\h* <ret> y jgh P }\n}\n\ndefine-command -hidden just-indent-on-new-line %{\n     evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # cleanup trailing white spaces on previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>\"_d }\n    }\n}\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/justfile regions\n\nadd-highlighter shared/justfile/content default-region group\nadd-highlighter shared/justfile/content/recipe regex '^@?([\\w-]+)([^\\n]*):(?!=)([^\\n]*)' 1:function 2:meta 3:keyword\nadd-highlighter shared/justfile/content/assignments regex ^([\\w-]+\\h*:=\\h*[^\\n]*) 1:meta\nadd-highlighter shared/justfile/content/operator regex '((^@|:=|=|\\+|\\(|\\)))' 1:operator\nadd-highlighter shared/justfile/content/strings regions\nadd-highlighter shared/justfile/content/strings/double region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/justfile/content/strings/single region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\n\nadd-highlighter shared/justfile/comment  region '#' '$'  fill comment\n\nadd-highlighter shared/justfile/inline   region '`' '`' ref sh\n\nadd-highlighter shared/justfile/body  region '^\\h+' '^[^\\h]' group\nadd-highlighter shared/justfile/body/interpreters regions\nadd-highlighter shared/justfile/body/interpreters/defaultshell default-region group\nadd-highlighter shared/justfile/body/interpreters/defaultshell/ ref sh\nadd-highlighter shared/justfile/body/interpreters/defaultshell/ regex '^\\h+(@)' 1:operator\n\nadd-highlighter shared/justfile/body/interpreters/bash region '^\\h+#!\\h?/usr/bin/env bash' '^[^\\h]' ref sh\nadd-highlighter shared/justfile/body/interpreters/sh region '^\\h+#!\\h?/usr/bin/env sh' '^[^\\h]' ref sh\n\nadd-highlighter shared/justfile/body/ regex '(\\{{2})([\\w-]+(?:\\(\\))?)(\\}{2})' 1:operator 2:variable 3:operator\n\n\n}\n"
  },
  {
    "path": "rc/filetype/kakrc.kak",
    "content": "# http://kakoune.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate (.*/)?(kakrc|.*\\.kak) %{\n    set-option buffer filetype kak\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=kak %~\n    require-module kak\n\n    set-option window static_words %opt{kak_static_words}\n\n    hook window InsertChar \\n -group kak-insert kak-insert-on-new-line\n    hook window InsertChar \\n -group kak-indent kak-indent-on-new-line\n    hook window InsertChar [>)}\\]] -group kak-indent kak-indent-on-closing-matching\n    hook window InsertChar (?![[{(<>)}\\]])[^\\s\\w] -group kak-indent kak-indent-on-closing-char\n    # cleanup trailing whitespaces on current line insert end\n    hook window ModeChange pop:insert:.* -group kak-trim-indent %{ try %{ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d } }\n    set-option buffer extra_word_chars '_' '-'\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window kak-.+ }\n~\n\nhook -group kak-highlight global WinSetOption filetype=kak %{\n    add-highlighter window/kakrc ref kakrc\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/kakrc }\n}\n\nprovide-module kak %§\n\nrequire-module sh\n\n# Highlighters & Completion\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/kakrc regions\nadd-highlighter shared/kakrc/code default-region group\nadd-highlighter shared/kakrc/comment region (^|\\h)\\K# $ fill comment\nadd-highlighter shared/kakrc/double_string region -recurse %{(?<!\")(\"\")+(?!\")} %{(^|\\h)\\K\"} %{\"(?!\")} group\nadd-highlighter shared/kakrc/single_string region -recurse %{(?<!')('')+(?!')} %{(^|\\h)\\K'} %{'(?!')} group\nadd-highlighter shared/kakrc/shell1 region -recurse '\\{' '(^|\\h)\\K%?%sh\\{' '\\}' ref sh\nadd-highlighter shared/kakrc/shell2 region -recurse '\\(' '(^|\\h)\\K%?%sh\\(' '\\)' ref sh\nadd-highlighter shared/kakrc/shell3 region -recurse '\\[' '(^|\\h)\\K%?%sh\\[' '\\]' ref sh\nadd-highlighter shared/kakrc/shell4 region -recurse '<'  '(^|\\h)\\K%?%sh<'  '>'  ref sh\nadd-highlighter shared/kakrc/shell5 region -recurse '\\{' '(^|\\h)\\K-?shell-script(-completion|-candidates)?\\h+%\\{' '\\}' ref sh\nadd-highlighter shared/kakrc/shell6 region -recurse '\\(' '(^|\\h)\\K-?shell-script(-completion|-candidates)?\\h+%\\(' '\\)' ref sh\nadd-highlighter shared/kakrc/shell7 region -recurse '\\[' '(^|\\h)\\K-?shell-script(-completion|-candidates)?\\h+%\\[' '\\]' ref sh\nadd-highlighter shared/kakrc/shell8 region -recurse '<'  '(^|\\h)\\K-?shell-script(-completion|-candidates)?\\h+%<'  '>'  ref sh\n\nevaluate-commands %sh{\n    # Grammar\n    keywords=\"add-highlighter alias arrange-buffers buffer buffer-next buffer-previous catch\n              change-directory colorscheme debug declare-option declare-user-mode define-command complete-command\n              delete-buffer delete-buffer! echo edit edit! enter-user-mode evaluate-commands execute-keys\n              fail hook info kill kill! map nop on-key prompt provide-module quit quit!\n              remove-highlighter remove-hooks rename-buffer rename-client rename-session require-module\n              select set-face set-option set-register source trigger-user-hook try\n              unalias unmap unset-face unset-option update-option\n              write write! write-all write-all-quit write-quit write-quit!\"\n    attributes=\"global buffer window current\n                normal insert prompt goto view user object\n                number-lines show-matching show-whitespaces fill regex dynregex group flag-lines\n                ranges line column wrap ref regions region default-region replace-ranges\"\n    types=\"int bool str regex int-list str-list completions line-specs range-specs str-to-str-map\"\n    values=\"default black red green yellow blue magenta cyan white yes no false true\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list kak_static_words $(join \"${keywords} ${attributes} ${types} ${values}\" ' ')'\"\n\n    # Highlight keywords (which are always surrounded by whitespace)\n    printf '%s\\n' \"add-highlighter shared/kakrc/code/keywords regex (?:\\s|\\A)\\K($(join \"${keywords}\" '|'))(?:(?=\\s)|\\z) 0:keyword\n                   add-highlighter shared/kakrc/code/attributes regex (?:\\s|\\A)\\K($(join \"${attributes}\" '|'))(?:(?=\\s)|\\z) 0:attribute\n                   add-highlighter shared/kakrc/code/types regex (?:\\s|\\A)\\K($(join \"${types}\" '|'))(?:(?=\\s)|\\z) 0:type\n                   add-highlighter shared/kakrc/code/values regex (?:\\s|\\A)\\K($(join \"${values}\" '|'))(?:(?=\\s)|\\z) 0:value\"\n}\n\nadd-highlighter shared/kakrc/code/colors regex \\b(rgb:[0-9a-fA-F]{6}|rgba:[0-9a-fA-F]{8})\\b 0:value\nadd-highlighter shared/kakrc/code/numbers regex \\b\\d+\\b 0:value\n\nadd-highlighter shared/kakrc/double_string/fill fill string\nadd-highlighter shared/kakrc/double_string/escape regex '\"\"' 0:default+b\nadd-highlighter shared/kakrc/single_string/fill fill string\nadd-highlighter shared/kakrc/single_string/escape regex \"''\" 0:default+b\n\nadd-highlighter shared/kak ref kakrc\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden kak-insert-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # copy '#' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*#\\h* <ret> y jgh P }\n    =\n~\n\ndefine-command -hidden kak-indent-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after line ending with %\\w*[^\\s\\w]\n        try %{ execute-keys -draft k x <a-k> \\%\\w*[^\\s\\w]$ <ret> j <a-gt> }\n        # deindent closing brace when after cursor\n        try %_ execute-keys -draft -itersel x <a-k> ^\\h*([>)}\\]]) <ret> gh / <c-r>1 <ret> m <a-S> 1<a-&> _\n        # deindent closing char(s) \n        try %{ execute-keys -draft -itersel x <a-k> ^\\h*([^\\s\\w]) <ret> gh / <c-r>1 <ret> <a-?> <c-r>1 <ret> <a-T>% <a-k> \\w*<c-r>1$ <ret> <a-S> 1<a-&> }\n    =\n~\n\ndefine-command -hidden kak-indent-on-closing-matching %~\n    # align to opening matching brace when alone on a line\n    try %= execute-keys -draft -itersel <a-h><a-k>^\\h*\\Q %val{hook_param} \\E$<ret> mGi s \\A|.\\z<ret> 1<a-&> =\n~\n\ndefine-command -hidden kak-indent-on-closing-char %{\n    # align to opening matching character when alone on a line\n    try %{ execute-keys -draft -itersel <a-h><a-k>^\\h*\\Q %val{hook_param} \\E$<ret>gi<a-f> %val{hook_param} <a-T>%<a-k>\\w*\\Q %val{hook_param} \\E$<ret> s \\A|.\\z<ret> gi 1<a-&> }\n}\n\n§\n"
  },
  {
    "path": "rc/filetype/kdl.kak",
    "content": "# https://kdl.dev\n# This supports both kdl v1 and v2.\n \nhook global BufCreate .*\\.kdl %{\n    set-option buffer filetype kdl\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=kdl %{\n    require-module kdl\n\n    hook window ModeChange pop:insert:.* -group kdl-trim-indent kdl-trim-indent\n    hook window InsertChar .* -group kdl-indent kdl-indent-on-char\n    hook window InsertChar \\n -group kdl-indent kdl-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window kdl-.+ }\n}\n\nhook -group kdl-highlight global WinSetOption filetype=kdl %{\n    add-highlighter window/kdl ref kdl\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/kdl }\n}\n\nprovide-module kdl %@\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/kdl regions\nadd-highlighter shared/kdl/code default-region group\n\n# Slashdash (/-) comments are annoying to highlight properly without a proper parser due to the fact that they can comment\n# out any kdl construct.\n# The below is an approximation, and there are almost certainly edge cases missed.\nadd-highlighter shared/kdl/slashdash_entire_node_with_child region -recurse \\{ ^[\\s]*/-[^\\n]+\\{ \\} fill comment\nadd-highlighter shared/kdl/slashdash_child region -recurse \\{ /-\\s*\\{ \\} fill comment\nadd-highlighter shared/kdl/slashdash_node region ^[\\s]*/-[^\\n]+ $ fill comment\nadd-highlighter shared/kdl/slashdash_string region '/-\\s*\"' (?<!\\\\)(\\\\\\\\)*\" fill comment\nadd-highlighter shared/kdl/slashdash_triple_string region '/-\\s*\"\"\"' '(?<!\\\\)(?:\\\\\\\\)*\"\"\"' fill string\nadd-highlighter shared/kdl/slashdash_raw_string region -match-capture '/-\\s*r\"(#+)' '\"(#+)' fill comment\nadd-highlighter shared/kdl/slashdash_raw_string2 region -match-capture '/-\\s*\"(#+)' '\"(#+)' fill comment\nadd-highlighter shared/kdl/slashdash_builtin_value region /-\\s+(true|false|null) \\s fill comment\nadd-highlighter shared/kdl/slashdash_binary region /-\\s*0b[01_]+ \\s fill comment\nadd-highlighter shared/kdl/slashdash_octal region /-\\s*0o[0-7_]+ \\s fill comment\nadd-highlighter shared/kdl/slashdash_hex region /-\\s*0x[a-fA-F0-9_]+ \\s fill comment\nadd-highlighter shared/kdl/slashdash_decimal region /-\\s*[0-9-+][0-9_]* \\s fill comment\nadd-highlighter shared/kdl/slashdash_float region /-\\s*[0-9-+][0-9_]*\\.[0-9_]+ \\s fill comment\nadd-highlighter shared/kdl/slashdash_float_exp region /-\\s*[0-9-+][0-9_]*(\\.[0-9_]+)?[eE][-+]?[0-9_]+ \\s fill comment\nadd-highlighter shared/kdl/slashdash_prop_string region '/-\\s*[\\u000021-\\u00FFFF]+=\"' (?<!\\\\)(\\\\\\\\)*\" fill comment\nadd-highlighter shared/kdl/slashdash_prop_triple_string region '/-\\s*[\\u000021-\\u00FFFF]+=\"\"\"' '(?<!\\\\)(\\\\\\\\)*\"\"\"' fill comment\nadd-highlighter shared/kdl/slashdash_prop_raw_string region -match-capture '/-\\s*[\\u000021-\\u00FFFF]+=r\"(#*)' '\"(#*)' fill comment\nadd-highlighter shared/kdl/slashdash_prop_raw_string2 region -match-capture '/-\\s*[\\u000021-\\u00FFFF]+=\"(#*)' '\"(#*)' fill comment\nadd-highlighter shared/kdl/slashdash_prop_other region /-\\s*[\\u000021-\\u00FFFF]+= \\s fill comment\nadd-highlighter shared/kdl/slashdash_arg region /-\\s*[\\u000021-\\u00FFFF]+ \\s fill comment\n\nadd-highlighter shared/kdl/raw_string region -match-capture 'r(#+)\"' '\"(#+)' fill string\nadd-highlighter shared/kdl/raw_string2 region -match-capture '(#+)\"' '\"(#+)' fill string\n\nadd-highlighter shared/kdl/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/kdl/triple_string region '\"\"\"' '(?<!\\\\)(?:\\\\\\\\)*\"\"\"' fill string\n\nadd-highlighter shared/kdl/comment region -recurse /\\* /\\* \\*/ fill comment\nadd-highlighter shared/kdl/line_comment region // $ fill comment\n\n\nadd-highlighter shared/kdl/code/node regex \\b([\\u000021-\\u00FFFF]*)\\b 0:variable # Everything not covered below is a node\nadd-highlighter shared/kdl/code/property regex \\b([\\u000021-\\u00FFFF]+)(=) 0:operator 1:attribute\nadd-highlighter shared/kdl/code/builtin_value regex [^\\w](#true|#false|#null|#inf|#-inf|#nan|true|false|null)\\b 0:value\nadd-highlighter shared/kdl/code/binary regex \\b(0b[01_]+)\\b 0:value\nadd-highlighter shared/kdl/code/octal regex \\b(0o[0-7_]+)\\b 0:value\nadd-highlighter shared/kdl/code/hex regex \\b(0x[a-fA-F0-9_]+)\\b 0:value\nadd-highlighter shared/kdl/code/decimal regex \\b([0-9-+][0-9_]*)\\b 0:value\nadd-highlighter shared/kdl/code/float regex \\b([0-9-+][0-9_]*\\.[0-9_]+)\\b 0:value\nadd-highlighter shared/kdl/code/float_exp regex \\b([0-9-+][0-9_]*(\\.[0-9_]+)?[eE][-+]?[0-9_]+)\\b 0:value\n\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden kdl-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden kdl-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m <a-S> 1<a-&> >\n    >\n>\n\ndefine-command -hidden kdl-indent-on-new-line %{ \n    evaluate-commands -itersel -draft %{\n        execute-keys <semicolon>\n        try %{\n            evaluate-commands -draft -save-regs '/\"' %{\n                # copy the commenting prefix\n                execute-keys -save-regs '' k x1s^(\\h*//+\\h*)<ret> y\n                try %{\n                    # if the previous comment isn't empty, create a new one\n                    execute-keys x<a-K>^\\h*//+\\h*$<ret> jxs^\\h*<ret>P\n                } catch %{\n                    # if there is no text in the previous comment, remove it completely\n                    execute-keys d\n                }\n            }\n\n            # trim trailing whitespace on the previous line\n            try %{ execute-keys -draft k x s\\h+$<ret> d }\n        }\n\n        try %{\n            # if the previous line isn't within a comment scope, break\n            execute-keys -draft kx <a-k>^(\\h*/\\*|\\h+\\*(?!/))<ret>\n\n            # find comment opening, validate it was not closed, and check its using star prefixes\n            execute-keys -draft <a-?>/\\*<ret><a-H> <a-K>\\*/<ret> <a-k>\\A\\h*/\\*([^\\n]*\\n\\h*\\*)*[^\\n]*\\n\\h*.\\z<ret>\n\n            try %{\n                # if the previous line is opening the comment, insert star preceeded by space\n                execute-keys -draft kx<a-k>^\\h*/\\*<ret>\n                execute-keys -draft i*<space><esc>\n            } catch %{\n               try %{\n                    # if the next line is a comment line insert a star\n                    execute-keys -draft jx<a-k>^\\h+\\*<ret>\n                    execute-keys -draft i*<space><esc>\n                } catch %{\n                    try %{\n                        # if the previous line is an empty comment line, close the comment scope\n                        execute-keys -draft kx<a-k>^\\h+\\*\\h+$<ret> x1s\\*(\\h*)<ret>c/<esc>\n                    } catch %{\n                        # if the previous line is a non-empty comment line, add a star\n                        execute-keys -draft i*<space><esc>\n                    }\n                }\n            }\n\n            # trim trailing whitespace on the previous line\n            try %{ execute-keys -draft k x s\\h+$<ret> d }\n            # align the new star with the previous one\n            execute-keys Kx1s^[^*]*(\\*)<ret>&\n        }\n    }\n}\n\n@\n"
  },
  {
    "path": "rc/filetype/kickstart.kak",
    "content": "hook global BufCreate .*\\.ks %{\n    set-option buffer filetype kickstart\n}\n\nhook global WinSetOption filetype=kickstart %{\n    require-module kickstart\n}\n\nhook -group kickstart-highlight global WinSetOption filetype=kickstart %{\n    add-highlighter window/kickstart ref kickstart\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/kickstart }\n}\n\n\nprovide-module kickstart %{\n\nadd-highlighter shared/kickstart regions\nadd-highlighter shared/kickstart/code default-region group\nadd-highlighter shared/kickstart/comment region '(^|\\h)\\K#' $ fill comment\nadd-highlighter shared/kickstart/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/kickstart/single_string region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/kickstart/packages region '^\\h*\\K%packages\\b' '^\\h*\\K%end\\b' group\nadd-highlighter shared/kickstart/shell region '^\\h*\\K%(pre|pre-install|post)\\b' '^\\h*\\K%end\\b' group\n\nadd-highlighter shared/kickstart/code/ regex \"^\\h*\\b(auth|authconfig|autopart|autostep|bootloader|btrfs|clearpart|cmdline|device|dmraid|driverdisk|fcoe|firewall|firstboot|group|graphical|halt|ignoredisk|install|cdrom|harddrive|liveimg|nfs|url|iscsi|iscsiname|keyboard|lang|logvol|logging|mediacheck|monitor|multipath|network|part|partition|poweroff|raid|realm|reboot|repo|rescue|rootpw|selinux|services|shutdown|sshkey|sshpw|skipx|text|timezone|updates|upgrade|user|vnc|volgroup|xconfig|zerombr|zfcp)\\b\" 1:keyword\nadd-highlighter shared/kickstart/code/ regex '(--[\\w-]+=? ?)([^-\"\\n][^\\h\\n]*)?' 1:attribute 2:string\nadd-highlighter shared/kickstart/code/ regex '%(include|ksappend)\\b' 0:keyword\n\nadd-highlighter shared/kickstart/packages/ regex \"^\\h*[\\w-]*\" 0:value\nadd-highlighter shared/kickstart/packages/ regex \"#[^\\n]*\" 0:comment\nadd-highlighter shared/kickstart/packages/ regex \"^\\h*@\\^?[\\h\\w-]*\" 0:attribute\nadd-highlighter shared/kickstart/packages/ regex '\\A\\h*\\K%packages\\b' 0:type\nadd-highlighter shared/kickstart/packages/ regex '^\\h*%end\\b' 0:type\nadd-highlighter shared/kickstart/shell/ regex '\\A\\h*\\K%(pre-install|pre|post)\\b' 0:type\nadd-highlighter shared/kickstart/shell/ regex '^\\h*%end\\b' 0:type\nadd-highlighter shared/kickstart/shell/ ref sh\n\n}\n"
  },
  {
    "path": "rc/filetype/kitty.kak",
    "content": "# https://sw.kovidgoyal.net/kitty/\n\nhook global BufCreate .*/kitty/.*[.]conf %{\n    set-option buffer filetype kitty-conf\n}\n\nhook global BufCreate .*/kitty/.*[.]session %{\n    set-option buffer filetype kitty-session\n}\n\nhook global WinSetOption filetype=kitty-conf %{\n    require-module kitty-conf\n    set-option window static_words %opt{kitty_conf_static_words}\n\n    hook window ModeChange pop:insert:.* -group kitty-conf-trim-indent kitty-conf-trim-indent\n    hook window InsertChar \\n -group kitty-conf-insert kitty-conf-insert-on-new-line\n    hook window InsertChar \\n -group kitty-conf-indent kitty-conf-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window kitty-conf-.+ }\n}\n\nhook -group kitty-conf-highlight global WinSetOption filetype=kitty-conf %{\n    add-highlighter window/kitty-conf ref kitty-conf\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/kitty-conf }\n}\n\nhook global WinSetOption filetype=kitty-session %{\n    require-module kitty-session\n    set-option window static_words %opt{kitty_session_static_words}\n\n    hook window ModeChange pop:insert:.* -group kitty-session-trim-indent kitty-session-trim-indent\n    hook window InsertChar \\n -group kitty-session-insert kitty-session-insert-on-new-line\n    hook window InsertChar \\n -group kitty-session-indent kitty-session-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window kitty-session-.+ }\n}\n\nhook -group kitty-session-highlight global WinSetOption filetype=kitty-session %{\n    add-highlighter window/kitty-session ref kitty-session\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/kitty-session }\n}\n\nprovide-module kitty-conf %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/kitty-conf regions\nadd-highlighter shared/kitty-conf/code default-region group\n\n\nadd-highlighter shared/kitty-conf/line_comment region ^# $ fill comment\nadd-highlighter shared/kitty-conf/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/kitty-conf/string2 region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\n\nadd-highlighter shared/kitty-conf/code/keybind regex ^\\s*map\\s([^\\s]+) 1:value\nadd-highlighter shared/kitty-conf/code/kitty_mod regex ^\\s*kitty_mod\\s([^\\s]+) 1:value\nadd-highlighter shared/kitty-conf/code/include regex ^\\s*include\\s(.*) 1:string\nadd-highlighter shared/kitty-conf/code/globinclude regex ^\\s*globinclude\\s(.*) 1:string\nadd-highlighter shared/kitty-conf/code/geninclude regex ^\\s*geninclude\\s(.*) 1:string\nadd-highlighter shared/kitty-conf/code/envinclude regex ^\\s*envinclude\\s(.*) 1:string\n\nadd-highlighter shared/kitty-conf/code/ regex \\b(yes|no|no-op)\\b 0:value\nadd-highlighter shared/kitty-conf/code/ regex '(?i)\\b0x[\\da-f]+\\b' 0:value\nadd-highlighter shared/kitty-conf/code/ regex '(?i)\\b0o?[0-7]+\\b' 0:value\nadd-highlighter shared/kitty-conf/code/ regex '(?i)\\b0b[01]+\\b' 0:value\nadd-highlighter shared/kitty-conf/code/ regex '(?i)\\bU\\+[\\da-f]+\\b' 0:value\nadd-highlighter shared/kitty-conf/code/ regex '(?i)[^\\w]#([\\da-f]{8}|[\\da-f]{6}|[\\da-f]{3})\\b' 0:value\nadd-highlighter shared/kitty-conf/code/ regex '(?i)\\s([1-9]\\d*|0)\\b' 0:value\nadd-highlighter shared/kitty-conf/code/ regex '(\\b\\d+)?\\.\\d+\\b' 0:value\n\nevaluate-commands %sh{\n    kitty_mods=\"alt super cmd command ⌘ control ctrl ^ kitty_mod opt option ⌥ shift ⇧\"\n    kitty_keywords=\"action_alias active_border_color active_tab_background\n                    active_tab_font_style active_tab_foreground active_tab_title_template\n                    allow_cloning allow_hyperlinks allow_remote_control background\n                    background_blur background_image background_image_layout\n                    background_image_linear background_opacity background_tint\n                    background_tint_gaps bell_border_color bell_on_tab bell_path bold_font\n                    bold_italic_font box_drawing_scale clear_all_mouse_actions\n                    clear_all_shortcuts clear_selection_on_clipboard_loss click_interval\n                    clipboard_control clipboard_max_size clone_source_strategies\n                    close_on_child_death color0 color1 color2 color3 color4 color5 color6\n                    color7 color8 color9 color10 color11 color12 color13 color14 color15\n                    color16 color17 color18 color19 color20 color21 color22 color23 color24\n                    color25 color26 color27 color28 color29 color30 color31 color32 color33\n                    color34 color35 color36 color37 color38 color39 color40 color41 color42\n                    color43 color44 color45 color46 color47 color48 color49 color50 color51\n                    color52 color53 color54 color55 color56 color57 color58 color59 color60\n                    color61 color62 color63 color64 color65 color66 color67 color68 color69\n                    color70 color71 color72 color73 color74 color75 color76 color77 color78\n                    color79 color80 color81 color82 color83 color84 color85 color86 color87\n                    color88 color89 color90 color91 color92 color93 color94 color95 color96\n                    color97 color98 color99 color100 color101 color102 color103 color104\n                    color105 color106 color107 color108 color109 color110 color111 color112\n                    color113 color114 color115 color116 color117 color118 color119 color120\n                    color121 color122 color123 color124 color125 color126 color127 color128\n                    color129 color130 color131 color132 color133 color134 color135 color136\n                    color137 color138 color139 color140 color141 color142 color143 color144\n                    color145 color146 color147 color148 color149 color150 color151 color152\n                    color153 color154 color155 color156 color157 color158 color159 color160\n                    color161 color162 color163 color164 color165 color166 color167 color168\n                    color169 color170 color171 color172 color173 color174 color175 color176\n                    color177 color178 color179 color180 color181 color182 color183 color184\n                    color185 color186 color187 color188 color189 color190 color191 color192\n                    color193 color194 color195 color196 color197 color198 color199 color200\n                    color201 color202 color203 color204 color205 color206 color207 color208\n                    color209 color210 color211 color212 color213 color214 color215 color216\n                    color217 color218 color219 color220 color221 color222 color223 color224\n                    color225 color226 color227 color228 color229 color230 color231 color232\n                    color233 color234 color235 color236 color237 color238 color239 color240\n                    color241 color242 color243 color244 color245 color246 color247 color248\n                    color249 color250 color251 color252 color253 color254 color255\n                    command_on_bell confirm_os_window_close copy_on_select cursor\n                    cursor_beam_thickness cursor_blink_interval cursor_shape\n                    cursor_shape_unfocused cursor_stop_blinking_after cursor_text_color\n                    cursor_trail cursor_trail_decay cursor_trail_start_threshold\n                    cursor_underline_thickness default_pointer_shape detect_urls dim_opacity\n                    disable_ligatures draw_minimal_borders dynamic_background_opacity editor\n                    enable_audio_bell enabled_layouts env exe_search_path\n                    file_transfer_confirmation_bypass filter_notification focus_follows_mouse\n                    font_family font_features font_size force_ltr foreground forward_stdio\n                    hide_window_decorations inactive_border_color inactive_tab_background\n                    inactive_tab_font_style inactive_tab_foreground inactive_text_alpha\n                    initial_window_height initial_window_width input_delay italic_font\n                    kitten_alias kitty_mod linux_bell_theme linux_display_server listen_on\n                    macos_colorspace macos_custom_beam_cursor macos_hide_from_tasks\n                    macos_menubar_title_max_length macos_option_as_alt\n                    macos_quit_when_last_window_closed macos_show_window_title_in\n                    macos_thicken_font macos_titlebar_color macos_traditional_fullscreen\n                    macos_window_resizable map mark1_background mark1_foreground\n                    mark2_background mark2_foreground mark3_background mark3_foreground\n                    menu_map modify_font mouse_hide_wait mouse_map narrow_symbols\n                    notify_on_cmd_finish open_url_with paste_actions placement_strategy\n                    pointer_shape_when_dragging pointer_shape_when_grabbed\n                    remember_window_size remote_control_password repaint_delay\n                    resize_debounce_time resize_in_steps scrollback_fill_enlarged_window\n                    scrollback_indicator_opacity scrollback_lines scrollback_pager\n                    scrollback_pager_history_size select_by_word_characters\n                    select_by_word_characters_forward selection_background\n                    selection_foreground shell shell_integration show_hyperlink_targets\n                    single_window_margin_width single_window_padding_width startup_session\n                    strip_trailing_spaces symbol_map sync_to_monitor tab_activity_symbol\n                    tab_bar_align tab_bar_background tab_bar_edge tab_bar_margin_color\n                    tab_bar_margin_height tab_bar_margin_width tab_bar_min_tabs tab_bar_style\n                    tab_fade tab_powerline_style tab_separator tab_switch_strategy\n                    tab_title_max_length tab_title_template term terminfo_type\n                    text_composition_strategy text_fg_override_threshold\n                    touch_scroll_multiplier transparent_background_colors undercurl_style\n                    underline_exclusion underline_hyperlinks update_check_interval url_color\n                    url_excluded_characters url_prefixes url_style visual_bell_color\n                    visual_bell_duration visual_window_select_characters watcher\n                    wayland_enable_ime wayland_titlebar_color wheel_scroll_min_lines\n                    wheel_scroll_multiplier window_alert_on_bell window_border_width\n                    window_logo_alpha window_logo_path window_logo_position window_logo_scale\n                    window_margin_width window_padding_width window_resize_step_cells\n                    window_resize_step_lines\"\n    kitty_actions=\"change_font_size clear_selection clear_terminal click close_os_window\n                   close_other_os_windows close_other_tabs_in_os_window\n                   close_other_windows_in_tab close_shared_ssh_connections close_tab\n                   close_window close_window_with_confirmation combine\n                   copy_and_clear_or_interrupt copy_ansi_to_clipboard copy_or_interrupt\n                   copy_to_buffer copy_to_clipboard create_marker debug_config detach_tab\n                   detach_window disable_ligatures_in discard_event doubleclick doublepress\n                   dump_lines_with_attrs edit_config_file eighth_window fifth_window\n                   first_window focus_visible_window fourth_window goto_layout goto_tab\n                   hide_macos_app hide_macos_other_apps input_unicode_character kitten\n                   kitty_shell last_used_layout launch layout_action load_config_file\n                   minimize_macos_window mouse_click_url mouse_click_url_or_select\n                   mouse_handle_click mouse_select_command_output mouse_selection\n                   mouse_show_command_output move_tab_backward move_tab_forward move_window\n                   move_window_backward move_window_forward move_window_to_top\n                   neighboring_window new_os_window new_os_window_with_cwd new_tab\n                   new_tab_with_cwd new_window new_window_with_cwd next_layout next_tab\n                   next_window ninth_window no-op nth_os_window nth_window open_url\n                   open_url_with_hints pass_selection_to_program paste paste_from_buffer\n                   paste_from_clipboard paste_from_selection paste_selection\n                   paste_selection_or_clipboard pop_keyboard_mode press previous_tab\n                   previous_window push_keyboard_mode quit release remote_control\n                   remote_control_script remove_marker reset_window_sizes resize_window\n                   scroll_end scroll_home scroll_line_down scroll_line_up scroll_page_down\n                   scroll_page_up scroll_prompt_to_bottom scroll_prompt_to_top scroll_to_mark\n                   scroll_to_prompt second_window select_tab send_key send_text\n                   set_background_opacity set_colors set_tab_title set_window_title\n                   seventh_window show_error show_first_command_output_on_screen\n                   show_kitty_doc show_kitty_env_vars show_last_command_output\n                   show_last_non_empty_command_output show_last_visited_command_output\n                   show_scrollback signal_child sixth_window sleep start_resizing_window\n                   swap_with_window tenth_window third_window toggle_fullscreen toggle_layout\n                   toggle_macos_secure_keyboard_entry toggle_marker toggle_maximized\n                   toggle_tab triplepress\"\n    kitty_include=\"envinclude geninclude globinclude include\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    printf %s\\\\n \"declare-option str-list kitty_conf_static_words $(join \"${kitty_mods}\" ' ') $(join \"${kitty_keywords}\" ' ') $(join \"${kitty_actions}\" ' ') $(join \"${kitty_include}\" ' ')\"\n\n    printf %s\\\\n \"add-highlighter shared/kitty-conf/code/ regex '\\b($(join \"${kitty_mods}\" '|'))\\b' 0:keyword\"\n    printf %s\\\\n \"add-highlighter shared/kitty-conf/code/ regex '\\b($(join \"${kitty_keywords}\" '|'))\\b' 0:keyword\"\n    printf %s\\\\n \"add-highlighter shared/kitty-conf/code/ regex '\\b($(join \"${kitty_actions}\" '|'))\\b' 0:function\"\n    printf %s\\\\n \"add-highlighter shared/kitty-conf/code/ regex '\\b($(join \"${kitty_include}\" '|'))\\b' 0:keyword\"\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden kitty-conf-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden kitty-conf-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy # comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden kitty-conf-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : kitty-conf-trim-indent <ret> }\n    }\n}\n\n}\n\nprovide-module kitty-session %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/kitty-session regions\nadd-highlighter shared/kitty-session/code default-region group\n\nadd-highlighter shared/kitty-session/line_comment region ^# $ fill comment\nadd-highlighter shared/kitty-session/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/kitty-session/string2 region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\n\nadd-highlighter shared/kitty-session/code/ regex '(?i)\\b0x[\\da-f]+\\b' 0:value\nadd-highlighter shared/kitty-session/code/ regex '(?i)\\b0o?[0-7]+\\b' 0:value\nadd-highlighter shared/kitty-session/code/ regex '(?i)\\b0b[01]+\\b' 0:value\nadd-highlighter shared/kitty-session/code/ regex '(?i)\\bU\\+[\\da-f]+\\b' 0:value\nadd-highlighter shared/kitty-session/code/ regex '(?i)\\s([1-9]\\d*|0)\\b' 0:value\nadd-highlighter shared/kitty-session/code/ regex '(\\b\\d+)?\\.\\d+\\b' 0:value\n\nevaluate-commands %sh{\n    kitty_session_commands=\"new_tab new_os_window layout launch focus enabled_layouts cd title os_window_size os_window_class\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n    \n    printf %s\\\\n \"declare-option str-list kitty_session_static_words $(join \"${kitty_session_commands}\" ' ')\"\n\n    printf %s\\\\n \"add-highlighter shared/kitty-session/code/ regex '\\b($(join \"${kitty_session_commands}\" '|'))\\b' 0:keyword\"\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden kitty-session-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden kitty-session-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy # comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden kitty-session-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : kitty-session-trim-indent <ret> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/kotlin.kak",
    "content": "# References --------------------------------------------------------------------------------------- #\n# ‾‾‾‾‾‾‾‾‾‾\n# Team: Yerlan & Kirk Duncan\n#\n# Kotlin 2020, Keywords and Operators, v1.4.0, viewed 9 September 2020, https://kotlinlang.org/docs/reference/keyword-reference.html\n# Kdoc 2020, Documenting Kotlin Code, Block Tags, v1.4.0, viewed 9 September 2020, https://kotlinlang.org/docs/reference/kotlin-doc.html\n# Oracle 2020, Java Platform, Standard Edition & Java Development Kit, Version 14 API Specification, viewed 8 September 2020, https://docs.oracle.com/en/java/javase/14/docs/api/index.html\n#\n# File types --------------------------------------------------------------------------------------- #\n# ‾‾‾‾‾‾‾‾‾‾\nhook global BufCreate .*[.](kt|kts)  %{\n  set-option buffer filetype kotlin\n}\n\n# Initialization ----------------------------------------------------------------------------------- #\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\nhook global WinSetOption filetype=kotlin %{\n  require-module kotlin\n\n  set-option window static_words %opt{kotlin_static_words}\n\n  # cleanup trailing whitespaces when exiting insert mode\n  hook window ModeChange pop:insert:.* -group kotlin-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n  hook window InsertChar \\n -group kotlin-indent kotlin-insert-on-new-line\n  hook window InsertChar \\n -group kotlin-indent kotlin-indent-on-new-line\n  hook window InsertChar \\{ -group kotlin-indent kotlin-indent-on-opening-curly-brace\n  hook window InsertChar \\} -group kotlin-indent kotlin-indent-on-closing-curly-brace\n\n  hook -once -always window WinSetOption filetype=.* %{ remove-hooks window kotlin-.+ }\n}\n\nhook -group kotlin-highlighter global WinSetOption filetype=kotlin %{\n  add-highlighter window/kotlin ref kotlin\n  add-highlighter window/kdoc ref kdoc\n\n  hook -once -always window WinSetOption filetype=.* %{\n    remove-highlighter window/kotlin\n    remove-highlighter window/kdoc\n  }\n}\n\nhook global BufSetOption filetype=kotlin %{\n  require-module kotlin\n\n  set-option buffer comment_line '//'\n  set-option buffer comment_block_begin '/*'\n  set-option buffer comment_block_end '*/'\n\n  hook -once -always buffer BufSetOption filetype=.* %{ remove-hooks buffer kotlin-.+ }\n}\n\n# Module ------------------------------------------------------------------------------------------- #\n# ‾‾‾‾‾‾\nprovide-module kotlin %§\n\nadd-highlighter shared/kotlin regions\nadd-highlighter shared/kotlin/code default-region group\nadd-highlighter shared/kotlin/string region %{(?<!')\"} %{(?<!\\\\)(\\\\\\\\)*\"} group\nadd-highlighter shared/kotlin/character region %{'} %{(?<!\\\\)'} group\nadd-highlighter shared/kotlin/comment region /\\* \\*/ fill comment\nadd-highlighter shared/kotlin/inline_documentation region /// $ fill documentation\nadd-highlighter shared/kotlin/line_comment region // $ fill comment\n\nadd-highlighter shared/kotlin/code/annotations regex @\\w+\\b|\\b\\w+@(?=\\{) 0:meta\nadd-highlighter shared/kotlin/code/identifiers regex \\b(field|it)\\b 1:variable\nadd-highlighter shared/kotlin/code/fields      regex \\.([A-Za-z_][\\w]*)\\s*?\\. 1:type\n\n# String interpolation\nadd-highlighter shared/kotlin/string/ fill string\nadd-highlighter shared/kotlin/string/ regex \\$\\{.*?\\} 0:value\n\n# Character\nadd-highlighter shared/kotlin/character/ fill value\nadd-highlighter shared/kotlin/character/ regex ('.{1})(.+)(') 2:meta\n\n# As at 15 March 2021, method see: https://regex101.com/r/Mhy4HG/1\nadd-highlighter shared/kotlin/code/methods     regex ::([A-Za-z_][\\w]*)|\\.([A-Za-z_][\\w]*)\\s*?[\\(\\{]|\\.([A-Za-z_][\\w]*)[\\s\\)\\}>](?=[^\\(\\{]) 1:function 2:function 3:function\n\n# Test suite functions: fun `this is a valid character function test`()\nadd-highlighter shared/kotlin/code/fun_tests   regex ^\\h*?fun\\s*?`(.[^<>:/\\[\\]\\\\\\.]+?)`\\h*?(?=\\() 1:default+iuf\nadd-highlighter shared/kotlin/code/delimiters  regex (\\(|\\)|\\[|\\]|\\{|\\}|\\;|') 1:operator\nadd-highlighter shared/kotlin/code/operators   regex (\\+|-|\\*|&|=|\\\\|\\?|%|\\|-|!|\\||->|\\.|,|<|>|:|\\^|/) 1:operator\nadd-highlighter shared/kotlin/code/numbers     regex \\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)([LlFf])?\\b 0:value\n\n# Generics need improvement, as after a colon will match as a constant only.\n# val program: IOU = XXXX; val cat: DOG = XXXX. matches IOU or DOG as a\n# CONSTANT when it could be generics. See: https://regex101.com/r/VPO5LE/10\nadd-highlighter shared/kotlin/code/constants_and_generics regex \\b((?<==\\h)\\([A-Z][A-Z0-9_]+(?=[<:\\;])|(?<!<)[A-Z][A-Z0-9_]+\\b(?!<[>\\)]))|\\b((?<!=\\s)(?<!\\.)[A-Z]+\\d*?(?![\\(\\;:])(?=[,\\)>\\s]))\\b 1:meta 2:type\n\nadd-highlighter shared/kotlin/code/target regex @(delegate|field|file|get|param|property|receiver|set|setparam)(?=:) 0:meta\nadd-highlighter shared/kotlin/code/soft   regex \\b(by|catch|constructor|dynamic|finally|get|import|init|set|where)\\b 1:keyword\nadd-highlighter shared/kotlin/code/hard   regex \\b(as|as\\?|break|class|continue|do|else|false|for|fun|if|in|!in|interface|is|!is|null|object|package|return|super|this|throw|true|try|typealias|val|var|when|while)\\b 1:keyword\n\nadd-highlighter shared/kotlin/code/modifier regex \\b(actual|abstract|annotation|companion|const|crossinline|data|enum|expect|external|final|infix|inline|inner|internal|lateinit|noinline|open|operator|out|override|private|protected|public|reified|sealed|suspend|tailrec|vararg)\\b(?=[\\s\\n]) 1:attribute\n\nadd-highlighter shared/kotlin/code/type regex \\b(Annotation|Any|Boolean|BooleanArray|Byte|ByteArray|Char|Character|CharArray|CharSequence|Class|ClassLoader|Cloneable|Comparable|Compiler|DeprecationLevel|Double|DoubleArray|Enum|Float|FloatArray|Function|Int|IntArray|Integer|Lazy|LazyThreadSafetyMode|Long|LongArray|Math|Nothing|Number|Object|Package|Pair|Process|Runnable|Runtime|SecurityManager|Short|ShortArray|StackTraceElement|StrictMath|String|StringBuffer|System|Thread|ThreadGroup|ThreadLocal|Triple|Unit|Void)\\b(?=[^<]) 1:type\n\n# Kdoc --------------------------------------------------------------------------------------------- #\n# ‾‾‾‾\nadd-highlighter shared/kdoc group\nadd-highlighter shared/kdoc/tag regex \\*(?:\\s+)?(@(author|constructor|exception|param|property|receiver|return|sample|see|since|suppress|throws))\\b 1:default+ui\n\n# Discolour ---------------------------------------------------------------------------------------- #\n# ‾‾‾‾‾‾‾‾‾\nadd-highlighter shared/kotlin/code/discolour regex ^(package|import)(?S)(.+) 2:default+fa\n\n# Commands ----------------------------------------------------------------------------------------- #\n# ‾‾‾‾‾‾‾‾\ndefine-command -hidden kotlin-insert-on-new-line %[\n  # copy // comments prefix and following white spaces\n  try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K/{2,}\\h* <ret> y<c-o>P<esc> }\n]\n\ndefine-command -hidden kotlin-indent-on-new-line %~\n  evaluate-commands -draft -itersel %<\n    # preserve previous line indent\n    try %{ execute-keys -draft <semicolon>K<a-&> }\n    # indent after lines ending with { or (\n    try %[ execute-keys -draft kx <a-k> [{(]\\h*$ <ret> j<a-gt> ]\n    # cleanup trailing white spaces on the previous line\n    try %{ execute-keys -draft kx s \\h+$ <ret>d }\n    # align to opening paren of previous line\n    try %{ execute-keys -draft [( <a-k> \\A\\([^\\n]+\\n[^\\n]*\\n?\\z <ret> s \\A\\(\\h*.|.\\z <ret> '<a-;>' & }\n    # indent after a pattern match on when/where statements\n    try %[ execute-keys -draft kx <a-k> ^\\h*(when|where).*$ <ret> j<a-gt> ]\n    # indent after term on an expression\n    try %[ execute-keys -draft kx <a-k> =\\h*?$ <ret> j<a-gt> ]\n    # indent after keywords\n    try %[ execute-keys -draft <semicolon><a-F>)MB <a-k> \\A(catch|do|else|for|if|try|while)\\h*\\(.*\\)\\h*\\n\\h*\\n?\\z <ret> s \\A|.\\z <ret> 1<a-&>1<a-,><a-gt> ]\n    # deindent closing brace(s) when after cursor\n    try %[ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> ]\n  >\n~\n\ndefine-command -hidden kotlin-indent-on-opening-curly-brace %[\n  # align indent with opening paren when { is entered on a new line after the closing paren\n  try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden kotlin-indent-on-closing-curly-brace %[\n  # align to opening curly brace when alone on a line\n  try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\n# Exceptions, Errors, and Types -------------------------------------------------------------------- #\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n# macro: 93le<a-;>i<ret><esc><esc>\nevaluate-commands %sh{\n\n  kotlin_keywords='abstract actual annotation as break by catch class companion const\n    constructor continue crossinline data delegate do dynamic else enum expect external\n    false field file final finally for fun get if import in infix init inline inner\n    interface internal is lateinit noinline null object open operator out override\n    package param private property protected public receiver reified return sealed set\n    setparam super suspend tailrec this throw true try typealias val var vararg when where while'\n\n  kotlin_types='Annotation Any Boolean BooleanArray Byte ByteArray Char Character CharArray\n    CharSequence Class ClassLoader Cloneable Comparable Compiler DeprecationLevel Double\n    DoubleArray Enum Float FloatArray Function Int IntArray Integer Lazy LazyThreadSafetyMode\n    Long LongArray Math Nothing Number Object Package Pair Process Runnable Runtime\n    SecurityManager Short ShortArray StackTraceElement StrictMath String StringBuffer System\n    Thread ThreadGroup ThreadLocal Triple Unit Void'\n\n  # ------------------------------------------------------------------------------------------------ #\n\n  kotlin_kdocs='author constructor exception param property receiver return sample see since suppress throws'\n\n  # ------------------------------------------------------------------------------------------------ #\n\n  kotlin_errors_exceptions='CharacterCodingException Error AssertionError NotImplementedError\n    OutOfMemoryErrorIllegalCallableAccessException IllegalPropertyDelegateAccessException\n    NoSuchPropertyException RuntimeException Throwable'\n\n  # ------------------------------------------------------------------------------------------------ #\n\n  join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n  # ------------------------------------------------------------------------------------------------ #\n\n  printf %s\\\\n \"declare-option str-list kotlin_static_words $(join \"${kotlin_keywords} ${kotlin_types} ${kotlin_kdocs} ${kotlin_errors_exceptions}\" ' ')\"\n\n  # ------------------------------------------------------------------------------------------------ #\n\n  printf %s\\\\n \"add-highlighter shared/kotlin/code/errors_exceptions regex \\b($(join \"${kotlin_errors_exceptions}\" '|'))\\b 0:type\"\n}\n§\n# ------------------------------------------------------------------------------------------------- #\n"
  },
  {
    "path": "rc/filetype/latex.kak",
    "content": "# https://www.latex-project.org/\n#\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.(tex|cls|sty|dtx) %{\n    set-option buffer filetype latex\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=latex %(\n    require-module latex\n\n    hook window InsertChar \\n -group latex-insert %{ latex-insert-on-newline }\n    hook window InsertChar \\n -group latex-indent %{ latex-indent-newline }\n    hook window InsertChar \\} -group latex-indent %{ latex-indent-closing-brace }\n    hook window ModeChange pop:insert:.* -group latex-indent %{ latex-trim-indent }\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window latex-indent }\n    hook window InsertChar \\n -group latex-insert latex-insert-on-new-line\n)\n\nhook -group latex-highlight global WinSetOption filetype=latex %{\n    add-highlighter window/latex ref latex\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/latex }\n}\n\nprovide-module latex %~\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/latex regions\nadd-highlighter shared/latex/content default-region group\n\n# Math modes\n# $math$ must have at least one non-$ character inside.\n# This is so that $$math$$ get matched properly.\nadd-highlighter shared/latex/dollar-math region '(?<!\\\\)(?:\\\\\\\\)*\\K\\$[^$]' '(?<!\\\\)(\\\\\\\\)*\\$' fill meta\nadd-highlighter shared/latex/ddollar-math region '(?<!\\\\)(?:\\\\\\\\)*\\K\\$\\$' '(?<!\\\\)(\\\\\\\\)*\\$\\$' fill meta\nadd-highlighter shared/latex/bracket-math region '(?<!\\\\)(?:\\\\\\\\)*\\K\\\\\\[' '(?<!\\\\)(\\\\\\\\)*\\\\\\]' fill meta\nadd-highlighter shared/latex/paren-math region '(?<!\\\\)(?:\\\\\\\\)*\\K\\\\\\(' '(?<!\\\\)(\\\\\\\\)*\\\\\\)' fill meta\n\n# Region for control sequence (includes latex2e arguments and options)\n# starting with unescaped \\ and ending :\n# - at eol, or\n# - at word boundaries not preceded nor followed by @ : \\ { } [ ] *, or\n# - after an unescaped }\nadd-highlighter shared/latex/cs region '(?<!\\\\)(?:\\\\\\\\)*\\K\\\\[@\\w]' '\\n|(?<![@:\\\\{}\\[\\]*])(?![@:\\\\{}\\[\\]*])\\b|(?<!\\\\)(?:\\\\\\\\)*\\K\\}\\K' group\nadd-highlighter shared/latex/comment region '(?<!\\\\)(?:\\\\\\\\)*\\K%' '\\n' fill comment\n\n# Document and LaTeX2e control sequence\nadd-highlighter shared/latex/cs/ regex '(?:\\\\[a-zA-Z@]+)' 0:keyword\n## Options passed to LaTeX2e control sequences, between brackets\nadd-highlighter shared/latex/cs/ regex '\\\\[a-zA-Z@]+\\b\\[([^\\]]+)\\]' 1:value\n## Emphasized text\nadd-highlighter shared/latex/cs/ regex '\\\\(?:emph|textit|textsl)\\{([^}]+)\\}' 1:default+i\n## Underlined text\nadd-highlighter shared/latex/cs/ regex '\\\\underline\\{([^}]+)\\}' 1:default+u\n## Bold text\nadd-highlighter shared/latex/cs/ regex '\\\\textbf\\{([^}]+)\\}' 1:default+b\n## Section headings\nadd-highlighter shared/latex/cs/ regex '\\\\(part|section)\\*?\\{([^}]+)\\}' 2:title\nadd-highlighter shared/latex/cs/ regex '\\\\(chapter|(sub)+section|(sub)*paragraph)\\*?\\{([^}]+)\\}' 4:header\n\n# LaTeX3 control sequence\n## Functions (expl3 doc) module_name:arguments_types.\nadd-highlighter shared/latex/cs/ regex '\\\\(?:__|@@_)?[a-zA-Z@]+_\\w+(:[nNpTFDwcVvxefo]+)?' 0:function 1:+db@type\n## Variables (expl3 doc): scope_name_type\nadd-highlighter shared/latex/cs/ regex '\\\\([lgc]_)[a-zA-Z@]+_\\w+' 0:variable 1:+db\n## l3kernel modules (l3kernel/doc/l3prefixes.csv)\nadd-highlighter shared/latex/cs/ regex '\\\\(alignment|alloc|ampersand|atsign|backslash|bitset|bool|box|catcode|cctab|char|chk|circumflex|clist|code|codedoc|coffin|colon|color|cs|debug|dim|document|dollar|driver|e|else|empty|etex|exp|expl|false|fi|file|flag|fp|group|hash|hbox|hcoffin|if|inf|initex|insert|int|intarray|ior|iow|job|kernel|keys|keyval|left|log|lua|luatex|mark|marks|math|max|minus|mode|msg|muskip|nan|nil|no|novalue|one|or|other|parameter|pdf|pdftex|peek|percent|pi|prg|prop|ptex|quark|recursion|ref|regex|reverse|right|scan|seq|skip|sort|space|stop|str|sys|tag|term|tex|text|tilde|tl|tmpa|tmpb|token|true|underscore|uptex|use|utex|vbox|vcoffin|xetex|zero)_' 0:+db\n# LaTeX3 types (expl3 doc)\nadd-highlighter shared/latex/cs/ regex '_(bool|box|cctab|clist|coffin|dim|fp|ior|iow|int|muskip|prop|seq|skip|str|tl)\\b' 0:+db\n\n# This belongs to content group as the LaTeX3 convention is separating macros names, args and options\n# with spaces and thus should not be catched by the cs region\n## macros arguments\nadd-highlighter shared/latex/content/ regex '(?<!\\\\)(?:\\\\\\\\)*\\K#+[1-9]' 0:string\n## group containing words and numbers (list separated by ; , / or spaces)\nadd-highlighter shared/latex/content/ regex '(?<!\\\\)(?:\\\\\\\\)*\\K\\{([\\s/;,.\\w\\d]+)\\}' 1:string\n\n# Indent\n# ------\n\ndefine-command -hidden latex-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        try %{ execute-keys x 1s^(\\h+)$<ret> d }\n    }\n}\n\ndefine-command -hidden latex-insert-on-newline %{\n    # copy '%' comment prefix and following white spaces\n    try %{ execute-keys -draft kx s^\\h*%\\h*<ret> y jgh P }\n}\n\ndefine-command -hidden latex-indent-newline %(\n    evaluate-commands -no-hooks -draft -itersel %(\n        # preserve previous line indent\n        try %{ execute-keys -draft K<a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft kx s\\h+$<ret> d }\n        # indent after line ending with {\n        try %( execute-keys -draft kx <a-k>\\{$<ret> j<a-gt> )\n        # deindent closing brace(s) when after cursor\n        try %( execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> )\n        # indent after line ending with \\begin{...}[...]{...}, with multiple\n        # sets of arguments possible\n        try %(\n            execute-keys -draft \\\n                kx \\\n                <a-k>\\\\begin\\h*\\{[^\\}]+\\}(\\h|\\[.*\\]|\\{.*\\})*$<ret> \\\n                j<a-gt>\n        )\n    )\n)\n\ndefine-command -hidden latex-indent-closing-brace %(\n    evaluate-commands -no-hooks -draft -itersel %(\n        # Align lone } with matching bracket\n        try %( execute-keys -draft x_ <a-k>\\A\\}\\z<ret> m<a-S>1<a-&> )\n        # Align \\end{...} with corresponding \\begin{...}\n        try %(\n            execute-keys -draft h<a-h> 1s\\\\end\\h*\\{([^\\}]+)\\}\\z<ret> \\\n                <a-?>\\\\begin\\s*\\{<c-r>.\\}<ret> <a-S>1<a-&>\n        )\n    )\n)\n\ndefine-command -hidden latex-insert-on-new-line %(\n    evaluate-commands -no-hooks -draft -itersel %(\n        # Wisely add \"\\end{...}\".\n        evaluate-commands -save-regs xz %(\n            # Save previous line indent in register x.\n            try %( execute-keys -draft kxs^\\h+<ret>\"xy ) catch %( reg x '' )\n            # Save item of begin in register z.\n            try %( execute-keys -draft kxs\\{.*\\}<ret>\"zy ) catch %( reg z '' )\n            try %(\n                # Validate previous line and that it is not closed yet.\n                execute-keys -draft kx <a-k>^<c-r>x\\h*\\\\begin\\{.*\\}<ret> J}iJx <a-K>^<c-r>x(\\\\end\\<c-r>z<backspace>\\})<ret>\n                # Auto insert \"\\end{...}\".\n                execute-keys -draft o<c-r>x\\end<c-r>z<esc>\n            )\n        )\n    )\n)\n\n~\n"
  },
  {
    "path": "rc/filetype/ledger.kak",
    "content": "# Detection\n# ---------\n\n# The .ledger suffix is not required by ledger, but the best I can do.\nhook global BufCreate .*\\.ledger %{\n    set-option buffer filetype ledger\n}\n\n# Initialization\n# --------------\n\nhook global WinSetOption filetype=ledger %{\n    require-module ledger\n\n    hook window InsertChar \\n -group ledger-indent ledger-indent-on-new-line\n    hook window ModeChange pop:insert:.* -group ledger-trim-indent ledger-trim-indent\n\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-hooks window ledger-.+\n        unset-option window static_words  # Remove static completion\n    }\n}\n\nhook -group ledger-highlight global WinSetOption filetype=ledger %{\n    add-highlighter window/ledger ref ledger\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-highlighter window/ledger\n    }\n}\n\n# Completion\n# ----------\n\nhook -group ledger-complete global WinSetOption filetype=ledger %{\n    set-option window static_words account note alias payee check assert eval \\\n        default apply fixed bucket capture comment commodity format nomarket \\\n        define end include tag test year\n}\n\nprovide-module ledger %[\n\n# Highlighters\n# ------------\n#\n# TODO: highlight tag comments\n\nadd-highlighter shared/ledger regions\n\n# The following highlighters implement\n# https://www.ledger-cli.org/3.0/doc/ledger3.html#Transactions-and-Comments\n\nadd-highlighter shared/ledger/transaction region '^[0-9]' '^(?=\\H)' group\nadd-highlighter shared/ledger/transaction/first_line regex \\\n    '^([0-9].*?)\\h.*?((  +|\\t+);.*?)?$' 1:function 2:string\nadd-highlighter shared/ledger/transaction/posting regex \\\n    '^\\h+([^\\h;].*?)((  +|\\t+).*?)?((  +|\\t+);.*?)?$' 1:type 2:value 4:string\nadd-highlighter shared/ledger/transaction/note regex '^\\h+;[^$]*?$' 0:string\n\nadd-highlighter shared/ledger/comment region '^(;|#|%|\\||\\*)' '$' fill comment\n\n# TODO: Improve\nadd-highlighter shared/ledger/other region '^(P|=|~)' '$' fill meta\n\n# The following highlighters implement\n# https://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives\n\nadd-highlighter shared/ledger/default default-region group\n\n# Add highlighters for simple one-line command directives\nevaluate-commands %sh{\n    # TODO: Is `expr` also a command directive? The documentation confuses me.\n    for cmd in 'apply account' 'apply fixed' 'assert' 'bucket' 'check' 'end' \\\n               'include' 'apply tag' 'test' 'year'; do\n        echo \"add-highlighter shared/ledger/default/ regex '^${cmd}\\b' 0:function\"\n    done\n}\n\nadd-highlighter shared/ledger/account region '^account' '^(?=\\H)' group\nadd-highlighter shared/ledger/account/first_line regex '^account'    0:function\nadd-highlighter shared/ledger/account/note       regex '^\\h*note'    0:function\nadd-highlighter shared/ledger/account/alias      regex '^\\h*alias'   0:function\nadd-highlighter shared/ledger/account/payee      regex '^\\h*payee'   0:function\nadd-highlighter shared/ledger/account/check      regex '^\\h*check'   0:function\nadd-highlighter shared/ledger/account/assert     regex '^\\h*assert'  0:function\nadd-highlighter shared/ledger/account/eval       regex '^\\h*eval'    0:function\nadd-highlighter shared/ledger/account/default    regex '^\\h*default' 0:function\n\nadd-highlighter shared/ledger/alias region '^alias' '$' group\nadd-highlighter shared/ledger/alias/keyword regex '^alias' 0:function\nadd-highlighter shared/ledger/alias/key regex '^alias\\h([^$=]*)=?' 1:variable\nadd-highlighter shared/ledger/alias/value regex '^alias\\h.*?=(.*?)$' 1:value\n\nadd-highlighter shared/ledger/capture region '^capture' '$' group\nadd-highlighter shared/ledger/capture/keyword regex '^capture' 0:function\nadd-highlighter shared/ledger/capture/account regex \\\n    '^capture\\h+(.*?)(  +|\\t+|$)' 1:type\nadd-highlighter shared/ledger/capture/regex regex \\\n    '^capture\\h+.*?(  +|\\t+)(.*?)$' 2:value\n\nadd-highlighter shared/ledger/comment_block region '^comment' '^end comment' \\\n    fill comment\n\nadd-highlighter shared/ledger/commodity region '^commodity' '^(?=\\H)' group\nadd-highlighter shared/ledger/commodity/first_line regex '^commodity'   0:function\nadd-highlighter shared/ledger/commodity/note       regex '^\\h*note'     0:function\nadd-highlighter shared/ledger/commodity/format     regex '^\\h*format'   0:function\nadd-highlighter shared/ledger/commodity/nomarket   regex '^\\h*nomarket' 0:function\nadd-highlighter shared/ledger/commodity/alias      regex '^\\h*alias'    0:function\nadd-highlighter shared/ledger/commodity/default    regex '^\\h*default'  0:function\n\nadd-highlighter shared/ledger/define region '^define' '$' group\nadd-highlighter shared/ledger/define/keyword regex '^define' 0:function\nadd-highlighter shared/ledger/define/key regex '^define\\h([^$=]*)=?' 1:variable\nadd-highlighter shared/ledger/define/value regex '^define\\h.*?=(.*?)$' 1:value\n\nadd-highlighter shared/ledger/payee region '^payee' '^(?=\\H)' group\nadd-highlighter shared/ledger/payee/first_line regex '^payee'    0:function\nadd-highlighter shared/ledger/payee/alias      regex '^\\h*alias' 0:function\nadd-highlighter shared/ledger/payee/uuid       regex '^\\h*uuid'  0:function\n\nadd-highlighter shared/ledger/tag region '^tag' '^(?=\\H)' group\nadd-highlighter shared/ledger/tag/first_line regex '^tag'       0:function\nadd-highlighter shared/ledger/tag/check      regex '^\\h*check'  0:function\nadd-highlighter shared/ledger/tag/assert     regex '^\\h*assert' 0:function\n\n# Commands\n# --------\n\ndefine-command -hidden ledger-indent-on-new-line %[\n    evaluate-commands -draft -itersel %[\n        # preserve previous line indent\n        try %[ execute-keys -draft <semicolon> K <a-&> ]\n        # cleanup trailing whitespaces from previous line\n        try %[ execute-keys -draft k x s \\h+$ <ret> d ]\n        # indent after the first line of a transaction\n        try %[ execute-keys -draft kx <a-k>^[0-9]<ret> j<a-gt> ]\n    ]\n]\n\ndefine-command -hidden ledger-trim-indent %{\n    try %{ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d }\n}\n\n]\n"
  },
  {
    "path": "rc/filetype/lisp.kak",
    "content": "# http://common-lisp.net\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](lisp) %{\n    set-option buffer filetype lisp\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=lisp %{\n    require-module lisp\n\n    hook window ModeChange pop:insert:.* -group lisp-trim-indent lisp-trim-indent\n    hook window InsertChar \\n -group lisp-indent lisp-indent-on-new-line\n    set-option buffer extra_word_chars '_' '+' '-' '*' '/' '@' '$' '%' '^' '&' '_' '=' '<' '>' '~' '.'\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window lisp-.+ }\n}\n\nhook -group lisp-highlight global WinSetOption filetype=lisp %{\n    add-highlighter window/lisp ref lisp\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/lisp }\n}\n\nprovide-module lisp %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/lisp regions\nadd-highlighter shared/lisp/code default-region group\nadd-highlighter shared/lisp/string region (?<!#\\\\)\" (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/lisp/comment region ';' '$' fill comment\n\nadd-highlighter shared/lisp/code/ regex (#?(['`:]|,@?))?\\b[a-zA-Z][\\w!$%&*+./:<=>?@^_~-]* 0:variable\nadd-highlighter shared/lisp/code/ regex \\b(nil|true|false)\\b 0:value\nadd-highlighter shared/lisp/code/ regex (((\\Q***\\E)|(///)|(\\Q+++\\E)){1,3})|(1[+-])|(<|>|<=|=|>=) 0:operator\nadd-highlighter shared/lisp/code/ regex \\b(def[a-z]+|if|do|let|lambda|catch|and|assert|while|def|do|fn|finally|let|loop|new|quote|recur|set!|throw|try|var|case|if-let|if-not|when|when-first|when-let|when-not|(cond(->|->>)?))\\b 0:keyword\nadd-highlighter shared/lisp/code/ regex \\*[a-zA-Z][\\w!$%&*+./:<=>?@^_~-]*\\* 0:variable\nadd-highlighter shared/lisp/code/ regex (\\b\\d+)?\\.\\d+([eEsSfFdDlL]\\d+)?\\b 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden lisp-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndeclare-option \\\n    -docstring 'regex matching the head of forms which have options *and* indented bodies' \\\n    regex lisp_special_indent_forms \\\n    '(?:def.*|if(-.*|)|let.*|lambda|with-.*|when(-.*|))'\n\ndefine-command -hidden lisp-indent-on-new-line %{\n    # registers: i = best align point so far; w = start of first word of form\n    evaluate-commands -draft -save-regs '/\"|^@iw' -itersel %{\n        execute-keys -draft 'gk\"iZ'\n        try %{\n            execute-keys -draft '[bl\"i<a-Z><gt>\"wZ'\n\n            try %{\n                # If a special form, indent another (indentwidth - 1) spaces\n                execute-keys -draft '\"wze<a-k>\\A' %opt{lisp_special_indent_forms} '\\z<ret>'\n                execute-keys -draft '\"wze<a-L>s.{' %sh{printf $(( kak_opt_indentwidth - 1 ))} '}\\K.*<ret><a-;>;\"i<a-Z><gt>'\n            } catch %{\n                # If not \"special\" form and parameter appears on line 1, indent to parameter\n                execute-keys -draft '\"wz<a-K>[()\\[\\]{}]<ret>e<a-l>s\\h\\K[^\\s].*<ret><a-;>;\"i<a-Z><gt>'\n            }\n        }\n        try %{ execute-keys -draft '[rl\"i<a-Z><gt>' }\n        try %{ execute-keys -draft '[Bl\"i<a-Z><gt>' }\n        execute-keys -draft ';\"i<a-z>a&,'\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/lua.kak",
    "content": "# http://lua.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](lua|rockspec) %{\n    set-option buffer filetype lua\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=lua %{\n    require-module lua\n\n    hook window ModeChange pop:insert:.* -group lua-trim-indent lua-trim-indent\n    hook window InsertChar .* -group lua-indent lua-indent-on-char\n    hook window InsertChar \\n -group lua-indent lua-indent-on-new-line\n    hook window InsertChar \\n -group lua-insert lua-insert-on-new-line\n\n    alias window alt lua-alternative-file\n\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-hooks window lua-.+\n        unalias window alt lua-alternative-file\n    }\n}\n\nhook -group lua-highlight global WinSetOption filetype=lua %{\n    add-highlighter window/lua ref lua\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/lua }\n}\n\n\nprovide-module lua %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/lua regions\nadd-highlighter shared/lua/code default-region group\nadd-highlighter shared/lua/raw_string  region -match-capture   '\\[(=*)\\[' '\\](=*)\\]' fill string\nadd-highlighter shared/lua/raw_comment region -match-capture '--\\[(=*)\\[' '\\](=*)\\]' fill comment\nadd-highlighter shared/lua/double_string region '\"'   (?<!\\\\)(?:\\\\\\\\)*\" fill string\nadd-highlighter shared/lua/single_string region \"'\"   (?<!\\\\)(?:\\\\\\\\)*' fill string\nadd-highlighter shared/lua/comment       region '--'  $                 fill comment\n\nadd-highlighter shared/lua/code/variable regex \\b\\w*\\b 0:variable # Everything in Lua is a variable!\nadd-highlighter shared/lua/code/function_declaration regex \\b(?:function\\h+)(?:\\w+\\h*\\.\\h*)*([a-zA-Z_]\\w*)\\( 1:function\nadd-highlighter shared/lua/code/function_call regex \\b([a-zA-Z_]\\w*)\\h*(?=[\\(\\{]) 1:function\nadd-highlighter shared/lua/code/keyword regex \\b(break|do|else|elseif|end|for|function|goto|if|in|local|repeat|return|then|until|while)\\b 0:keyword\nadd-highlighter shared/lua/code/value regex \\b(false|nil|true|self|[0-9]+(:?\\.[0-9])?(:?[eE]-?[0-9]+)?|0x[0-9a-fA-F]+)\\b 0:value\nadd-highlighter shared/lua/code/symbolic_operator regex (\\+|-|\\*|/|%|\\^|==?|~=|<=?|>=?|\\.\\.\\.?|#) 0:operator\nadd-highlighter shared/lua/code/keyword_operator regex \\b(and|or|not)\\b 0:operator\nadd-highlighter shared/lua/code/module regex \\b(_G|_ENV)\\b 0:module\nadd-highlighter shared/lua/code/attribute regex \\B(<[a-zA-Z_]\\w*>)\\B 0:attribute\nadd-highlighter shared/lua/code/label regex \\s(::\\w*::) 1:meta\nadd-highlighter shared/lua/code/goto_label regex \"\\bgoto (\\w*)\\b\" 1:meta\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command lua-alternative-file -docstring 'Jump to the alternate file (implementation ↔ test)' %{ evaluate-commands %sh{\n    case $kak_buffile in\n        *spec/*_spec.lua)\n            altfile=$(eval printf %s\\\\n $(printf %s\\\\n $kak_buffile | sed s+spec/+'*'/+';'s/_spec//))\n            [ ! -f $altfile ] && echo \"fail 'implementation file not found'\" && exit\n        ;;\n        *.lua)\n            altfile=\"\"\n            altdir=\"\"\n            path=$kak_buffile\n            dirs=$(while [ $path ]; do printf %s\\\\n $path; path=${path%/*}; done | tail -n +2)\n            for dir in $dirs; do\n                altdir=$dir/spec\n                if [ -d $altdir ]; then\n                    altfile=$altdir/$(realpath $kak_buffile --relative-to $dir | sed s+[^/]'*'/++';'s/.lua$/_spec.lua/)\n                    break\n                fi\n            done\n            [ ! -d \"$altdir\" ] && echo \"fail 'spec/ not found'\" && exit\n        ;;\n        *)\n            echo \"fail 'alternative file not found'\" && exit\n        ;;\n    esac\n    printf %s\\\\n \"edit $altfile\"\n}}\n\ndefine-command -hidden lua-trim-indent %[\n    # remove trailing whitespaces\n    try %[ execute-keys -draft -itersel x s \\h+$ <ret> d ]\n]\n\ndefine-command -hidden lua-indent-on-char %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # unindent middle and end structures\n        try %[ execute-keys -draft \\\n            <a-h><a-k>^\\h*(\\b(end|else|elseif|until)\\b|[)}])$<ret> \\\n            :lua-indent-on-new-line<ret> \\\n            <a-lt>\n        ]\n    ]\n]\n\ndefine-command -hidden lua-indent-on-new-line %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # remove trailing white spaces from previous line\n        try %[ execute-keys -draft k : lua-trim-indent <ret> ]\n        # preserve previous non-empty line indent\n        try %[ execute-keys -draft ,gh<a-?>^[^\\n]+$<ret>s\\A|.\\z<ret>)<a-&> ]\n        # add one indentation level if the previous line is not a comment and:\n        #     - starts with a block keyword that is not closed on the same line,\n        #     - or contains an unclosed function expression,\n        #     - or ends with an enclosed '(' or '{'\n        try %[ execute-keys -draft \\\n            , Kx \\\n            <a-K>\\A\\h*--<ret> \\\n            <a-K>\\A[^\\n]*\\b(end|until)\\b<ret> \\\n            <a-k>\\A(\\h*\\b(do|else|elseif|for|(local\\h+)?function|if|repeat|while)\\b|[^\\n]*[({]$|[^\\n]*\\bfunction\\b\\h*[(])<ret> \\\n            <a-:><semicolon><a-gt>\n        ]\n    ]\n]\n\ndefine-command -hidden lua-insert-on-new-line %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # copy -- comment prefix and following white spaces\n        try %[ execute-keys -draft kxs^\\h*\\K--\\h*<ret> y gh j x<semicolon> P ]\n        # wisely add end structure\n        evaluate-commands -save-regs x %[\n            # save previous line indent in register x\n            try %[ execute-keys -draft kxs^\\h+<ret>\"xy ] catch %[ reg x '' ]\n            try %[\n                # check that starts with a block keyword that is not closed on the same line\n                execute-keys -draft \\\n                    kx \\\n                    <a-k>^\\h*\\b(else|elseif|do|for|(local\\h+)?function|if|while)\\b|[^\\n]\\bfunction\\b\\h*[(]<ret> \\\n                    <a-K>\\bend\\b<ret>\n                # check that the block is empty and is not closed on a different line\n                execute-keys -draft <a-a>i <a-K>^[^\\n]+\\n[^\\n]+\\n<ret> jx <a-K>^<c-r>x\\b(else|elseif|end)\\b<ret>\n                # auto insert end\n                execute-keys -draft o<c-r>xend<esc>\n                # auto insert ) for anonymous function\n                execute-keys -draft kx<a-k>\\([^)\\n]*function\\b<ret>jjA)<esc>\n            ]\n        ]\n    ]\n]\n\n§\n"
  },
  {
    "path": "rc/filetype/mail.kak",
    "content": "hook global BufCreate .+\\.eml %{\n    set-option buffer filetype mail\n}\n\nhook global WinSetOption filetype=mail %{\n    require-module mail\n    map buffer normal <ret> :diff-jump<ret>\n    hook -once -always window WinSetOption filetype=.* %{\n        unmap buffer normal <ret> :diff-jump<ret>\n    }\n}\n\nhook -group mail-highlight global WinSetOption filetype=mail %{\n    add-highlighter window/mail ref mail\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/mail }\n}\n\n\nprovide-module mail %{\n\nrequire-module diff\n\nadd-highlighter shared/mail group\nadd-highlighter shared/mail/ ref diff\nadd-highlighter shared/mail/ regex ^(From|To|Cc|Bcc|Subject|Reply-To|In-Reply-To|References|Date|Message-Id|User-Agent):([^\\n]*(?:\\n\\h+[^\\n]+)*)$ 1:keyword 2:attribute\nadd-highlighter shared/mail/ regex <[a-zA-Z0-9!#$%&'*+/=?^`{|}~.-]+@[a-zA-Z0-9!#$%&'*+/=?^`{|}~.-]+> 0:string\nadd-highlighter shared/mail/ regex ^>.*?$ 0:comment\nadd-highlighter shared/mail/ regex ^--\\ \\n.* 0:comment\n\n}\n"
  },
  {
    "path": "rc/filetype/makefile.kak",
    "content": "# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*(/?[mM]akefile|\\.mk|\\.make) %{\n    set-option buffer filetype makefile\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=makefile %{\n    require-module makefile\n\n    set-option window static_words %opt{makefile_static_words}\n\n    hook window ModeChange pop:insert:.* -group makefile-trim-indent makefile-trim-indent\n    hook window InsertChar \\n -group makefile-indent makefile-indent-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window makefile-.+ }\n}\n\nhook -group makefile-highlight global WinSetOption filetype=makefile %{\n    add-highlighter window/makefile ref makefile\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/makefile }\n}\n\nprovide-module makefile %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/makefile regions\n\nadd-highlighter shared/makefile/content default-region group\nadd-highlighter shared/makefile/comment region (?<!\\\\)(?:\\\\\\\\)*(?:^|\\h)\\K# '$' fill comment\nadd-highlighter shared/makefile/evaluate-commands region -recurse \\( (?<!\\$)(?:\\$\\$)*\\K\\$\\( \\) fill value\n\nadd-highlighter shared/makefile/content/ regex ^\\S.*?(::|:|!)\\s 0:variable\nadd-highlighter shared/makefile/content/ regex [+?:]= 0:operator\n\nevaluate-commands %sh{\n    # Grammar\n    keywords=\"ifeq|ifneq|ifdef|ifndef|else|endif|define|endef\"\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list makefile_static_words ${keywords}\" | tr '|' ' '\n\n    # Highlight keywords\n    printf %s \"add-highlighter shared/makefile/content/ regex \\b(${keywords})\\b 0:keyword\"\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden makefile-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden makefile-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        ## If the line above is a target indent with a tab\n        try %{ execute-keys -draft Z kx <a-k>^\\S.*?(::|:|!)\\s<ret> z i<tab> }\n        # cleanup trailing white space son previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        # indent after some keywords\n        try %{ execute-keys -draft Z kx <a-k> ^\\h*(ifeq|ifneq|ifdef|ifndef|else|define)\\b<ret> z <a-gt> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/markdown.kak",
    "content": "# http://daringfireball.net/projects/markdown\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](markdown|md|mkd|Rmd) %{\n    set-option buffer filetype markdown\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=markdown %{\n    require-module markdown\n\n    hook window ModeChange pop:insert:.* -group markdown-trim-indent markdown-trim-indent\n    hook window InsertChar \\n -group markdown-insert markdown-insert-on-new-line\n    hook window InsertChar \\n -group markdown-indent markdown-indent-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window markdown-.+ }\n}\n\nhook -group markdown-load-languages global WinSetOption filetype=markdown %{\n    markdown-load-languages '%'\n}\n\nhook -group markdown-load-languages global WinSetOption filetype=markdown %{\n    hook -group markdown-load-languages window NormalIdle .* %{markdown-load-languages gtGbGl}\n    hook -group markdown-load-languages window InsertIdle .* %{markdown-load-languages gtGbGl}\n}\n\n\nhook -group markdown-highlight global WinSetOption filetype=markdown %{\n    add-highlighter window/markdown ref markdown\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/markdown }\n}\n\n\nprovide-module markdown %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/markdown regions\nadd-highlighter shared/markdown/inline default-region regions\nadd-highlighter shared/markdown/inline/text default-region group\n\nadd-highlighter shared/markdown/listblock region ^\\h*[-*]\\s ^(?=\\S) regions\nadd-highlighter shared/markdown/listblock/g default-region group\nadd-highlighter shared/markdown/listblock/g/ ref markdown/inline\nadd-highlighter shared/markdown/listblock/g/marker regex ^\\h*([-*])\\s 1:bullet\n\nadd-highlighter shared/markdown/codeblock region -match-capture \\\n    ^(\\h*)```\\h* \\\n    ^(\\h*)```\\h*$ \\\n    regions\nadd-highlighter shared/markdown/codeblock/ default-region fill meta\nadd-highlighter shared/markdown/listblock/codeblock region -match-capture \\\n    ^(\\h*)```\\h* \\\n    ^(\\h*)```\\h*$ \\\n    regions\nadd-highlighter shared/markdown/listblock/codeblock/ default-region fill meta\nadd-highlighter shared/markdown/codeline region \"^( {4}|\\t)\" \"$\" fill meta\n\n# https://spec.commonmark.org/0.29/#link-destination\nadd-highlighter shared/markdown/angle_bracket_url region (?<=<)([a-z]+://|(mailto|magnet|xmpp):) (?!\\\\).(?=>)|\\n fill link\nadd-highlighter shared/markdown/inline/url region -recurse \\( (\\b[a-z]+://|(mailto|magnet|xmpp):) (?!\\\\).(?=\\))|\\s fill link\nadd-highlighter shared/markdown/listblock/angle_bracket_url region (?<=<)(\\b[a-z]+://|(mailto|magnet|xmpp):) (?!\\\\).(?=>)|\\n fill link\n\ntry %{\n    require-module html\n    add-highlighter shared/markdown/inline/tag region (?i)</?[a-z][a-z0-9-]*\\s*([a-z_:]|(?=>)) > ref html/tag\n}\n\nadd-highlighter shared/markdown/inline/code region -match-capture (`+) (`+) fill mono\n\n# Setext-style header\nadd-highlighter shared/markdown/inline/text/ regex (\\A|^\\n)[^\\n]+\\n={2,}\\h*\\n\\h*$ 0:title\nadd-highlighter shared/markdown/inline/text/ regex (\\A|^\\n)[^\\n]+\\n-{2,}\\h*\\n\\h*$ 0:header\n\n# Atx-style header\nadd-highlighter shared/markdown/inline/text/ regex ^#[^\\n]* 0:header\n\nadd-highlighter shared/markdown/inline/text/ regex (?<!\\*)(\\*([^\\s*]|([^\\s*](\\n?[^\\n*])*[^\\s*]))\\*)(?!\\*) 1:+i\nadd-highlighter shared/markdown/inline/text/ regex (?<!_)(_([^\\s_]|([^\\s_](\\n?[^\\n_])*[^\\s_]))_)(?!_) 1:+i\nadd-highlighter shared/markdown/inline/text/ regex (?<!\\*)(\\*\\*([^\\s*]|([^\\s*](\\n?[^\\n*])*[^\\s*]))\\*\\*)(?!\\*) 1:+b\nadd-highlighter shared/markdown/inline/text/ regex (?<!_)(__([^\\s_]|([^\\s_](\\n?[^\\n_])*[^\\s_]))__)(?!_) 1:+b\nadd-highlighter shared/markdown/inline/text/ regex ^\\h*(>\\h*)+ 0:comment\nadd-highlighter shared/markdown/inline/text/ regex \"\\H( {2,})$\" 1:+r@meta\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command markdown-load-languages -params 1 %{\n    evaluate-commands -draft %{ try %{\n        execute-keys \"%arg{1}1s```\\h*\\{?[.=]?(\\w+)\\}?<ret>\"\n        evaluate-commands -itersel %{ try %{\n            require-module %val{selection}\n            add-highlighter \"shared/markdown/codeblock/%val{selection}\" region -match-capture \"^(\\h*)```\\h*(%val{selection}\\b|\\{[.=]?%val{selection}(?:[\\h,][^\\{\\}]*)?\\})\" ^(\\h*)``` regions\n            add-highlighter \"shared/markdown/codeblock/%val{selection}/\" default-region fill meta\n            add-highlighter \"shared/markdown/codeblock/%val{selection}/inner\" region \\A\\h*```[^\\n]*\\K (?=```) ref %val{selection}\n            add-highlighter \"shared/markdown/listblock/codeblock/%val{selection}\" region -match-capture \"^(\\h*)```\\h*(%val{selection}\\b|\\{[.=]?%val{selection}\\})\" ^(\\h*)``` regions\n            add-highlighter \"shared/markdown/listblock/codeblock/%val{selection}/\" default-region fill meta\n            add-highlighter \"shared/markdown/listblock/codeblock/%val{selection}/inner\" region \\A\\h*```[^\\n]*\\K (?=```) ref %val{selection}\n        }}\n    }}\n}\n\ndefine-command -hidden markdown-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden markdown-insert-on-new-line %{\n    try %{ execute-keys -draft -itersel k x s ^\\h*\\K((>\\h*)+([*+-]\\h)?|(>\\h*)*[*+-]\\h)\\h* <ret> y gh j P }\n}\n\ndefine-command -hidden markdown-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # remove trailing white spaces\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/mercurial.kak",
    "content": "# https://www.mercurial-scm.org/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*hg-editor-.*\\.txt$ %{\n    set-option buffer filetype hg-commit\n}\n\nhook global WinSetOption filetype=hg-commit %{\n    require-module hg-commit\n}\n\nhook -group hg-commit-highlight global WinSetOption filetype=hg-commit %{\n    add-highlighter window/hg-commit ref hg-commit\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/hg-commit-highlight }\n}\n\nprovide-module hg-commit %{\n\n# Faces\n# ‾‾‾‾‾\n\nset-face global MercurialCommitComment cyan\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/hg-commit regions\nadd-highlighter shared/hg-commit/comments region ^HG:\\  $ group\nadd-highlighter shared/hg-commit/comments/ fill comment\nadd-highlighter shared/hg-commit/comments/ regex \\\n\t\"\\b(?:(changed)|(removed)|(added)|(bookmark)|(branch)|(user:)) ([^\\n]*)$\" \\\n\t      1:yellow  2:red     3:green 4:blue     5:magenta 6:white\n\n}\n"
  },
  {
    "path": "rc/filetype/mercury.kak",
    "content": "# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](m) %{\n    set-option buffer filetype mercury\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=mercury %{\n    require-module mercury\n\n    set-option window static_words %opt{mercury_static_words}\n\n    hook window InsertChar \\n -group mercury-insert mercury-insert-on-new-line\n    hook window InsertChar \\n -group mercury-indent mercury-indent-on-new-line\n    # cleanup trailing whitespaces on current line insert end\n    hook window ModeChange pop:insert:.* -group mercury-trim-indent %{ try %{ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d } }\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window mercury-.+ }\n}\n\nhook -group mercury-highlight global WinSetOption filetype=mercury %{\n    add-highlighter window/mercury ref mercury\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/mercury }\n}\n\n\nprovide-module mercury %§\n\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/mercury regions\nadd-highlighter shared/mercury/code default-region group\nadd-highlighter shared/mercury/comment region '%' '$' fill comment\nadd-highlighter shared/mercury/line_comment region '%' '$' fill comment\n\nadd-highlighter shared/mercury/string region '\"' (?<!\\\\)(\\\\\\\\)*\"  fill string\n# Integer formats\nadd-highlighter shared/mercury/code/ regex '\\b0b[01]+\\b' 0:value\nadd-highlighter shared/mercury/code/ regex '\\b0x[\\da-f]+\\b' 0:value\nadd-highlighter shared/mercury/code/ regex '\\b0o?[0-7]+\\b' 0:value\nadd-highlighter shared/mercury/code/ regex '\\b([1-9]\\d*|0)(i8|i16|i32|i64|u|u8|u16|u32|u64)?\\b' 0:value\n# Float formats\nadd-highlighter shared/mercury/code/ regex '\\b(\\d+_\\d+)*\\d+[eE][+-]?\\d+(\\d+_\\d+)*\\b' 0:value\nadd-highlighter shared/mercury/code/ regex '(\\b\\d+)\\.\\d+\\b' 0:value\nadd-highlighter shared/mercury/code/ regex  '`[A-Za-z][A-Za-z_0-9]+`' 0:operator\n\nadd-highlighter shared/mercury/code/ regex \\b[a-z][A-Za-z_0-9]*(?=\\.\\w) 0:module\nadd-highlighter shared/mercury/code/ regex \\b[a-z][A-Za-z_0-9]*(?=__\\w) 0:module\n# func() and pred()\nadd-highlighter shared/mercury/code/ regex \\b(pred|func)(?=\\() 0:builtin\nadd-highlighter shared/mercury/code/ regex \\b[A-Z][A-Za-z_0-9]*\\b 0:variable\n\n# operator symbols\nadd-highlighter shared/mercury/code/ regex (\\.|!|!\\.|!:|@|\\^|:|\\*\\*|\\\\|\\*|/|//|<<|>>|\\+|\\+\\+|-|--|/\\\\|\\\\/|\\.\\.|:=|=\\^|<|=|=\\.\\.|=:=|=<|==|=\\\\=|>|>=|@<|@=<|@>|@>=|\\\\=|\\\\==|~=|\\\\\\+|~|<=|<=>|=>|,|&|->|\\;|::|==>|--->|-->|:-|\\?-) 0:operator\n\nevaluate-commands %sh{\n    # Grammar\n    values=\"true fail\"\n\n    # There is overlap between all of these. Not sure what to do about that.\n    operators=\"event div mod rem for is and or impure semipure not when all arbitrary atomic disable_warning disable_warnings promise_equivalent_solutions promise_equivalent_solution_setspromise_exclusive promise_exclusive_exhaustive promise_exhaustive promise_impure promise_pure promise_semipure require_complete_switch require_switch_arms_det require_switch_arms_semidet require_switch_arms_multi require_switch_arms_nondet require_switch_arms_cc_multi require_switch_arms_cc_nondet require_switch_arms_erroneous require_switch_arms_failure require_det require_semidet require_multi require_nondet require_cc_multi require_cc_nondet require_erroneous require_failure trace try some or_else then if else where catch catch_any initialize finalize rule solver type pred func inst mode typeclass instance pragma promise initialise finalise mutable module import_module use_module include_module\"\n    modes=\"free bound in out di mdi uo muo\"\n    determinisms=\"erroneous failure det semidet multi cc_multi nondet cc_nondet\"\n    reserved_insts=\"any bound bound_unique clobbered clobbered_any free ground is mostly_clobbered mostly_unique mostly_unique_any not_reached unique unique_any\"\n    reserved_modes=\"any_func any_pred func is pred\"\n    reserved_types=\"int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float character string pred func pure semipure impure\"\n\n    declarations=\"type solver type pred func inst mode typeclass instance pragma promise initialise finalise mutable module interface implementation import_module use_module include_module end_module\"\n \n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list mercury_static_words $(join \"${values} ${declarations} ${operators} ${determinisms} ${modes} ${reserved_insts} ${reserved_modes}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/mercury/code/ regex '\\b($(join \"${values}\" '|'))\\b' 0:value\n        add-highlighter shared/mercury/code/ regex '^:-(\\s+($(join \"${declarations}\" '|')))+\\b' 1:keyword\n        add-highlighter shared/mercury/code/ regex '\\b($(join \"${operators}\" '|'))\\b' 0:operator\n        add-highlighter shared/mercury/code/ regex '\\b($(join \"${determinisms}\" '|'))\\b' 0:keyword\n        add-highlighter shared/mercury/code/ regex '\\b($(join \"${modes}\" '|'))\\b' 0:keyword\n\n        # These overlap with previous. Not sure what the solution is.\n        # add-highlighter shared/mercury/code/ regex '\\b($(join \"${reserved_types}\" '|'))\\b' 0:type\n        # add-highlighter shared/mercury/code/ regex '\\b($(join \"${reserved_insts}\" '|'))\\b' 0:builtin\n        # add-highlighter shared/mercury/code/ regex '\\b($(join \"${reserved_modes}\" '|'))\\b' 0:builtin\n\n        # Implementation-defined literals - don't know how to get these to work\n        # add-highlighter shared/mercury/code/ regex '\\$(file|line|module|pred)' 0:keyword\n    \"\n}\n\ndefine-command -hidden mercury-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy '%' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\%\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden mercury-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %/ execute-keys -draft <a-h> <a-k> ^\\h+[]}]$ <ret> m s \\A|.\\z <ret> 1<a-&> /\n    >\n>\n\ndefine-command -hidden mercury-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after line ending with :-\n        try %{ execute-keys -draft , k x <a-k> :-$ <ret> j <a-gt> }\n        # deindent closing brace/bracket when after cursor\n        try %< execute-keys -draft x <a-k> ^\\h*[}\\])] <ret> gh / [}\\])] <ret> m <a-S> 1<a-&> >\n    >\n>\n\n§\n"
  },
  {
    "path": "rc/filetype/meson.kak",
    "content": "# meson syntax highlighting for kakoune (https://mesonbuild.com)\n#\n# For reference see:\n# https://mesonbuild.com/Syntax.html\n# https://github.com/mesonbuild/meson/blob/master/data/syntax-highlighting/vim/syntax/meson.vim\n\n## Detection\n\nhook global BufCreate (.*/|^)(meson\\.build|meson_options\\.txt) %{\n  set-option buffer filetype meson\n}\n\n## Initialization\n\nhook -group meson-highlight global WinSetOption filetype=meson %{\n    require-module meson\n\n    set-option window static_words %opt{meson_static_words}\n\n    add-highlighter window/meson ref meson\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/meson }\n}\n\nprovide-module meson %§\n\n## Highlighters\n\nadd-highlighter shared/meson regions\nadd-highlighter shared/meson/code default-region group\n\nadd-highlighter shared/meson/comment region '(\\h|^)#' '$' fill comment\n\n# TODO: highlight escape sequences within strings\nadd-highlighter shared/meson/string_multiline region \"'''\" \"'''\" fill string\nadd-highlighter shared/meson/string_single    region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\n\n# integer literals\nadd-highlighter shared/meson/code/ regex '\\b\\d+\\b' 0:value\n\n# operators\nadd-highlighter shared/meson/code/ regex '(?:\\+|-|\\*|/|%|!=|=|<|>|\\?|:)' 0:operator\nadd-highlighter shared/meson/code/ regex '\\b(?:and|not|or|in)\\b' 0:operator\n\n# functions\nadd-highlighter shared/meson/code/ regex \"\\b(\\w+)\\(\" 1:function\n\nevaluate-commands %sh{\n    values=\"true false\"\n\n    keywords=\"if else endif elif foreach endforeach break continue\"\n\n    builtins=\"add_global_arguments add_global_link_arguments add_languages\n    add_project_arguments add_project_link_arguments add_test_setup\n    alias_target assert benchmark both_libraries build_machine build_target\n    configuration_data configure_file custom_target declare_dependency\n    dependency disabler environment error executable files find_library\n    find_program generator get_option get_variable gettext host_machine\n    import include_directories install_data install_headers install_man\n    install_subdir is_disabler is_variable jar join_paths library meson\n    message option project run_command run_target set_variable shared_library\n    shared_module static_library subdir subdir_done subproject summary\n    target_machine test vcs_tag warning\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list meson_static_words $(join \"${values} ${keywords} ${builtins}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/meson/code/ regex '\\b($(join \"${values}\" '|'))\\b' 0:value\n        add-highlighter shared/meson/code/ regex '\\b($(join \"${keywords}\" '|'))\\b' 0:keyword\n        add-highlighter shared/meson/code/ regex '\\b($(join \"${builtins}\" '|'))\\b\\(' 1:builtin\n    \"\n}\n\n§\n"
  },
  {
    "path": "rc/filetype/mlb.kak",
    "content": "# http://mlton.org/MLBasis\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.mlb %{\n    set-option buffer filetype mlb\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=mlb %{\n    require-module mlb\n    set-option buffer extra_word_chars '_' '-' '.'\n    set-option window static_words %opt{mlb_static_words}\n}\n\nhook -group mlb-highlight global WinSetOption filetype=mlb %{\n    add-highlighter window/mlb ref mlb\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/mlb }\n}\n\nprovide-module mlb %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/mlb regions\nadd-highlighter shared/mlb/code default-region group\nadd-highlighter shared/mlb/string region '\"' '(?<!\\\\)(\\\\\\\\)*\"' group\nadd-highlighter shared/mlb/string/fill fill string\nadd-highlighter shared/mlb/comment region -recurse '\\(\\*' '\\(\\*' '\\*\\)' fill comment\n\nevaluate-commands %sh{\n    keywords='basis bas and open local let in end structure signature functor ann'\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    printf %s\\\\n \"declare-option str-list mlb_static_words $(join \"${keywords}\" ' ')\"\n    printf %s\\\\n \"add-highlighter shared/mlb/code/ regex (?<![\\w'-/.])($(join \"${keywords}\" '|'))(?![\\w'-/.]) 0:keyword\"\n}\nadd-highlighter shared/mlb/code/ regex \"=\" 0:operator\nadd-highlighter shared/mlb/code/ regex \"\\b([A-Z][\\w']*)\\b\" 0:type\nadd-highlighter shared/mlb/code/ regex \"\\b[A-Z]{2}[A-Z0-9_']+\\b\" 0:attribute\nadd-highlighter shared/mlb/code/ regex \"\\$\\(\\w+\\)\" 0:variable\nadd-highlighter shared/mlb/string/ regex \"\\$\\(\\w*\\)\" 0:variable\n\n]\n"
  },
  {
    "path": "rc/filetype/moon.kak",
    "content": "# http://moonscript.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](moon) %{\n    set-option buffer filetype moon\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=moon %{\n    require-module moon\n\n    hook window ModeChange pop:insert:.* -group moon-trim-indent moon-trim-indent\n    hook window InsertChar .* -group moon-indent moon-indent-on-char\n    hook window InsertChar \\n -group moon-insert moon-insert-on-new-line\n    hook window InsertChar \\n -group moon-indent moon-indent-on-new-line\n\n    alias window alt moon-alternative-file\n\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-hooks window moon-.+\n        unalias window alt moon-alternative-file\n    }\n}\n\nhook -group moon-highlight global WinSetOption filetype=moon %{\n    add-highlighter window/moon ref moon\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/moon }\n}\n\n\nprovide-module moon %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/moon regions\nadd-highlighter shared/moon/code default-region group\nadd-highlighter shared/moon/double_string region '\"'  (?<!\\\\)(\\\\\\\\)*\" regions\nadd-highlighter shared/moon/single_string region \"'\"  (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/moon/comment       region '--' '$'             fill comment\n\nadd-highlighter shared/moon/double_string/base default-region fill string\nadd-highlighter shared/moon/double_string/interpolation region -recurse \\{ \\Q#{ \\} fill meta\n\nadd-highlighter shared/moon/code/ regex \\\\\\w+ 0:function\nadd-highlighter shared/moon/code/ regex [\\W\\)\\}]\\h+\\K\\.\\w+ 0:function\nadd-highlighter shared/moon/code/ regex (\\+|-|\\*|/|%|\\^|==?|[~!]=|<=?|>=?|\\.\\.\\.?|#|!) 0:operator\nadd-highlighter shared/moon/code/ regex [-=]> 0:function\nadd-highlighter shared/moon/code/ regex \\b\\w+: 0:variable\nadd-highlighter shared/moon/code/ regex \\w+\\h*(?=[\\(!]) 0:function\nadd-highlighter shared/moon/code/ regex (?<!\\w)[@:]\\w+ 0:variable\nadd-highlighter shared/moon/code/ regex (?<!\\w)[@:]__(name|class|inherited):? 0:meta\nadd-highlighter shared/moon/code/ regex (?<!\\w)@@(\\w+:?)? 0:meta\nadd-highlighter shared/moon/code/ regex (\\w+)\\h*=\\h*(?:\\(.*?\\)\\h*)?[-=]> 1:function\nadd-highlighter shared/moon/code/ regex \\b(and|break|class|continue|do|else(if)?|export|extends|for|from|if|import|in|local|not|or|return|switch|then|unless|using|when|while|with)\\b 0:keyword\nadd-highlighter shared/moon/code/ regex \\b(true|false|nil|super|self)\\b 0:value\nadd-highlighter shared/moon/code/ regex \\b([0-9]+(:?\\.[0-9])?(:?[eE]-?[0-9]+)?|0x[0-9a-fA-F]+)\\b 0:value\nadd-highlighter shared/moon/code/ regex class(\\h+\\w+)?(?:\\h+extends(\\h+\\w+))?\\h*$ 1:type 2:attribute\nadd-highlighter shared/moon/code/ regex \\b(_G|_ENV)\\b 0:module\nadd-highlighter shared/moon/code/ regex ^\\h*export\\h+[\\*^]\\h*$ 0:meta\nadd-highlighter shared/moon/code/ regex ^\\h*local\\h+\\*\\h*$ 0:meta\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command moon-alternative-file -docstring 'Jump to the alternate file (implementation ↔ test)' %{ evaluate-commands %sh{\n    case $kak_buffile in\n        *spec/*_spec.moon)\n            altfile=$(eval printf %s\\\\n $(printf %s\\\\n $kak_buffile | sed s+spec/+'*'/+';'s/_spec//))\n            [ ! -f $altfile ] && echo \"fail 'implementation file not found'\" && exit\n        ;;\n        *.moon)\n            path=$kak_buffile\n            dirs=$(while [ $path ]; do printf %s\\\\n $path; path=${path%/*}; done | tail -n +2)\n            for dir in $dirs; do\n                altdir=$dir/spec\n                if [ -d $altdir ]; then\n                    altfile=$altdir/$(realpath $kak_buffile --relative-to $dir | sed s+[^/]'*'/++';'s/.moon$/_spec.moon/)\n                    break\n                fi\n            done\n            [ ! -d $altdir ] && echo \"fail 'spec/ not found'\" && exit\n        ;;\n        *)\n            echo \"fail 'alternative file not found'\" && exit\n        ;;\n    esac\n    printf %s\\\\n \"edit $altfile\"\n}}\n\ndefine-command -hidden moon-trim-indent %{\n    evaluate-commands -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden moon-indent-on-char %{\n    evaluate-commands -draft -itersel %{\n        # align _else_ statements to start\n        try %{ execute-keys -draft x <a-k> ^ \\h * (else(if)?) $ <ret> <a-semicolon> <a-?> ^ \\h * (if|unless|when) <ret> s \\A | \\z <ret> ) <a-&> }\n        # align _when_ to _switch_ then indent\n        try %{ execute-keys -draft x <a-k> ^ \\h * (when) $ <ret> <a-semicolon> <a-?> ^ \\h * (switch) <ret> s \\A | \\z <ret> ) <a-&> ) , <gt> }\n    }\n}\n\ndefine-command -hidden moon-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy -- comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K--\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden moon-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : moon-trim-indent <ret> }\n        # indent after start structure\n        try %{ execute-keys -draft k x <a-k> ^ \\h * (class|else(if)?|for|if|switch|unless|when|while|with) \\b | ([:=]|[-=]>) $ <ret> j <a-gt> }\n        # deindent after return statements\n        try %{ execute-keys -draft k x <a-k> ^ \\h * (break|return) \\b <ret> j <a-lt> }\n    }\n}\n\n]\n"
  },
  {
    "path": "rc/filetype/nim.kak",
    "content": "# https://nim-lang.org/\n#\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.nim(s|ble)? %{\n    set-option buffer filetype nim\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=nim %{\n    require-module nim\n\n    set-option window static_words %opt{nim_static_words}\n\n    hook window InsertChar \\n -group nim-insert nim-insert-on-new-line\n    hook window InsertChar \\n -group nim-indent nim-indent-on-new-line\n    # cleanup trailing whitespaces on current line insert end\n    hook window ModeChange pop:insert:.* -group nim-trim-indent %{ try %{ exec -draft <semicolon> x s ^\\h+$ <ret> d } }\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window nim-.+ }\n}\n\nhook -group nim-highlight global WinSetOption filetype=nim %{\n    add-highlighter window/nim ref nim\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/nim }\n}\n\nprovide-module nim %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/nim regions\nadd-highlighter shared/nim/code default-region group\nadd-highlighter shared/nim/triple_string region '([A-Za-z](_?\\w)*)?\"\"\"' '\"\"\"(?!\")' fill string\nadd-highlighter shared/nim/raw_string region [A-Za-z](_?[A-Za-z])*\" (?<!\")\"(?!\") fill string\nadd-highlighter shared/nim/string region (?<!'\\\\)\" ((?<!\\\\)(\\\\\\\\)*\"|$) group\nadd-highlighter shared/nim/inline_documentation region '##' $ fill documentation\nadd-highlighter shared/nim/documentation region '##\\[' '\\]##' fill documentation\nadd-highlighter shared/nim/comment region '#\\[' '\\]#' group\nadd-highlighter shared/nim/comment_line region (?<![^'].')#(?!'\\[) $ group\n\nadd-highlighter shared/nim/string/fill fill string\nadd-highlighter shared/nim/comment/fill fill comment\nadd-highlighter shared/nim/comment_line/fill fill comment\n\nevaluate-commands %sh{\n    # Grammar\n    opchars='[=+-/<>@$~&%|!?^.:\\\\*]'\n    opnocol='[=+-/<>@$~&%|!?^.\\\\*]'\n    letter='A-Za-z\\u000080-\\u10FFFF'\n    customsuffix=\"'[${letter}](_?[${letter}0-9])*\"\n    suffix=\"(${customsuffix}|[iIuU](8|16|32|64)|[fF](32|64)?|[dDuU])?\"\n    floatsuffix=\"(${customsuffix}|[fF](32|64)?|[dD])?\"\n    hexdigit='[0-9a-fA-F]'\n    octdigit='[0-7]'\n    bindigit='[01]'\n    hexlit=\"0[xX]${hexdigit}(_?${hexdigit})*\"\n    declit=\"\\d(_?\\d)*\"\n    octlit=\"0o${octdigit}(_?${octdigit})*\"\n    binlit=\"0[bB]${bindigit}(_?${bindigit})*\"\n    intlit=\"\\b(${declit}|${hexlit}|${octlit}|${binlit})${suffix}\\b\"\n    exponent=\"([eE][+-]?${declit})\"\n    floatlit=\"\\b${declit}(\\.${declit}${exponent}?|${exponent})${floatsuffix}\\b\"\n\n    keywords=\"addr asm bind block break case cast concept const continue\n    converter defer discard distinct do elif else end enum except export\n    finally for func if import include interface iterator let macro\n    method mixin nil out proc ptr raise ref return static template try type\n    unsafeAddr using var when while yield with without atomic generic\"\n    operators=\"or xor and is isnot in notin of div mod shl shr not as from\"\n    types=\"int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float\n    float32 float64 bool char object seq array cstring string tuple varargs\n    typedesc pointer byte set typed untyped void auto\"\n    values=\"false true on off\"\n\n    join() { sep=$2; set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    static_words=\"$(join \"${keywords} ${types} ${operator} ${values}\" ' ')\"\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list nim_static_words ${static_words}\"\n\n    keywords=\"$(join \"${keywords}\" '|')\"\n    operators=\"$(join \"${operators}\" '|')\"\n    types=\"$(join \"${types}\" '|')\"\n    values=\"$(join \"${values}\" '|')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/nim/code/ regex ${opchars}+ 0:operator\n        add-highlighter shared/nim/code/ regex (?<!${opchars}):{1,2}(?!${opchars}) 0:meta\n        add-highlighter shared/nim/code/ regex :${opnocol}${opchars}* 0:operator\n        add-highlighter shared/nim/code/ regex (?<!${opchars})(\\*)(:)(?!${opchars}) 1:operator 2:meta\n        add-highlighter shared/nim/code/ regex \\b(${keywords})\\b 0:keyword\n        add-highlighter shared/nim/code/ regex \\b(${operators})\\b 0:operator\n        add-highlighter shared/nim/code/ regex \\b(${types})\\b 0:type\n        add-highlighter shared/nim/code/ regex \\b(${values})\\b 0:value\n        add-highlighter shared/nim/code/ regex ${intlit} 0:value\n        add-highlighter shared/nim/code/ regex ${floatlit} 0:value\n    \"\n}\n\nadd-highlighter shared/nim/code/ regex '(,|;|`|\\(\\.?|\\.?\\)|\\[[.:]?|\\.?\\]|\\{\\.?|\\.?\\})' 0:meta\nadd-highlighter shared/nim/code/ regex %{'(\\\\([rcnlftvabe\\\\\"']|0*[12]?\\d?\\d|x[0-9a-fA-F]{2})|[^'\\n])'} 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden nim-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy '#' comment prefix and following white spaces\n        try %{ exec -draft k x s ^\\h*#\\h* <ret> y jgh P }\n    }\n}\n\ndefine-command -hidden nim-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ exec -draft <semicolon> K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ exec -draft k x s \\h+$ <ret> d }\n        # indent after line ending with enum, tuple, object, type, import, export, const, let, var, ':' or '='\n        try %{ exec -draft , k x <a-k> (:|=|\\b(?:enum|tuple|object|const|let|var|import|export|type))$ <ret> j <a-gt> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/ninja.kak",
    "content": "# ref: https://ninja-build.org/manual.html#ref_ninja_file\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .+\\.ninja %{\n    set-option buffer filetype ninja\n}\n\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=ninja %{\n    require-module ninja\n\n    set-option window static_words %opt{ninja_static_words}\n\n    hook window ModeChange pop:insert:.* -group ninja-trim-indent ninja-trim-indent\n    hook window InsertChar \\n -group ninja-insert ninja-insert-on-new-line\n    hook window InsertChar \\n -group ninja-indent ninja-indent-on-new-line\n    # cleanup trailing whitespaces on current line insert end\n    hook window ModeChange pop:insert:.* -group ninja-trim-indent %{ try %{ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d } }\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window ninja-.+ }\n}\n\nhook -group ninja-highlight global WinSetOption filetype=ninja %{\n    add-highlighter window/ninja ref ninja\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/ninja }\n}\n\n\nprovide-module ninja %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/ninja regions\n\n# `#`\nadd-highlighter shared/ninja/comment region '#' '\\n' fill comment\n\n# `subninja`, `include`, `rule`, `pool` and `default` declarations\nadd-highlighter shared/ninja/sirpd region '^(subninja|include|rule|pool|default)\\b' '(?<!\\$)\\n' group\nadd-highlighter shared/ninja/sirpd/default regex '^(subninja|include)\\b' 0:module\nadd-highlighter shared/ninja/sirpd/rulepool regex '^(rule|pool|default)\\b' 0:keyword\nadd-highlighter shared/ninja/sirpd/linebreak regex '\\$$' 0:operator\n\n# `build`\nadd-highlighter shared/ninja/build region '^build\\b' '(?<!\\$)\\n' group\nadd-highlighter shared/ninja/build/build regex '^build\\b' 0:keyword\nadd-highlighter shared/ninja/build/rule regex ':\\h+((\\w|-)+)' 0:function\nadd-highlighter shared/ninja/build/colonpipe regex ':|\\||\\|\\|' 0:operator\nadd-highlighter shared/ninja/build/linebreak regex '\\$$' 0:operator\nadd-highlighter shared/ninja/build/variables regex '\\$(\\w|-)+|\\$\\{(\\w|-)+\\}' 0:value\n\n# variables declarations\nadd-highlighter shared/ninja/variable region '^\\h*(\\w|-)+\\h*=' '(?<!\\$)\\n' group\nadd-highlighter shared/ninja/variable/declaredname regex '^\\h*((\\w|-)+)\\h*(=)' 1:variable 0:operator\nadd-highlighter shared/ninja/variable/linebreak regex '\\$$' 0:operator\nadd-highlighter shared/ninja/variable/variables regex '\\$(\\w|-)+|\\$\\{(\\w|-)+\\}' 0:value\n\n# keywords/builtin variable names\nevaluate-commands %sh{\n  keywords=\"rule build command default subninja include\"\n  reserved_names=\"builddir ninja_required_version pool depfile deps depfile msvc_deps_prefix description dyndep generator restat rspfile rspfile_content\"\n\n  printf %s \"\n      declare-option str-list ninja_static_words ${keywords} ${reserved_names}\n  \"\n\n  reserved_names_regex=\"$(echo ${reserved_names} | tr ' ' '|')\"\n  printf %s \"\n      add-highlighter shared/ninja/variable/reserved_names regex '^\\h*(${reserved_names_regex})\\b' 0:meta\n  \"\n}\n\n\n# Indent\n# ‾‾‾‾‾‾\n\ndefine-command -hidden ninja-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden ninja-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy -- comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K--\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden ninja-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft \\; K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : ninja-trim-indent <ret> }\n        # indent after lines begining with rule and pool\n        try %{ execute-keys -draft \\; k x <a-k> ^(rule|pool|build)\\b <ret> j <a-gt> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/nix.kak",
    "content": "# Nix package manager language\n# https://nixos.org/nix/manual/\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](nix) %{\n    set-option buffer filetype nix\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=nix %{\n    require-module nix\n\n    hook window ModeChange pop:insert:.* -group nix-trim-indent nix-trim-indent\n    hook window InsertChar .* -group nix-indent nix-indent-on-char\n    hook window InsertChar \\n -group nix-insert nix-insert-on-new-line\n    hook window InsertChar \\n -group nix-indent nix-indent-on-new-line\n\n    set-option buffer extra_word_chars _ -\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window nix-.+ }\n}\n\nhook -group nix-highlight global WinSetOption filetype=nix %{\n    add-highlighter window/nix ref nix\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/nix }\n}\n\nprovide-module nix %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/nix regions\nadd-highlighter shared/nix/code  default-region group\n# Define strings. They can contain interpolated nix code,\n# which itself can contain strings.\n# Note that we currently cannot properly support nesting of the same-delimiter strings\n# because of https://github.com/mawww/kakoune/issues/1670\nadd-highlighter shared/nix/double_string region '\"'    (?<!\\\\)(?:\\\\\\\\)*\"         regions\n# this is hard one: it is terminated by '', but ''$, ''\\* and ''' are escapes.\nadd-highlighter shared/nix/indent_string region \"''\"   (?<!')(?:''')*''(?![\\\\'$]) regions\nadd-highlighter shared/nix/comment1      region '#'    '$'                       fill comment\nadd-highlighter shared/nix/comment2      region /\\*    \\*/                       fill comment\n\n\n#add-highlighter shared/nix/code/ regex \"([a-zA-Z_][a-zA-Z0-9-_.]*)\\s*([=])\" 1:variable\nadd-highlighter shared/nix/code/ regex \"(\\b|-)[0-9]*\\.?[0-9eE]+\\b\" 0:value\n\nadd-highlighter shared/nix/double_string/str default-region fill string\nadd-highlighter shared/nix/double_string/variable region -recurse \\{ (?<!\\\\)(\\\\\\\\)*\\$\\{ \\} ref nix\nadd-highlighter shared/nix/indent_string/str default-region fill string\n# FIXME: the opening regex is not ideal. See https://nixos.org/nix/manual/#idm140737317975776\n# It should usually match \"${\", should match \"'''${\" (as \"'''\" is escaped itself),\n# but should not match \"''${\" and \"''\\${\".\n# Seems that negative lookbehind semantics is not enough for some complex cases.\nadd-highlighter shared/nix/indent_string/variable region -recurse \\{ (?<![^']'')\\$\\{ \\} ref nix\n\nadd-highlighter shared/nix/code/ regex \\b(true|false|null|let|in|with|if|then|else)\\b 0:keyword\nadd-highlighter shared/nix/code/ regex \\b(rec)\\b\\s*\\{ 1:keyword\n# Those are builtin functions available in global scope.\n# They should not be assigned to.\nadd-highlighter shared/nix/code/ regex '[^.]\\s*\\b(builtins|inherit|baseNameOf|derivation|dirOf|fetchTarball|import|isNull|map|removeAttrs|throw|toString)\\b\\s*[^=]' 1:builtin\n\nadd-highlighter shared/nix/code/ regex '\\b\\s*(\\.)\\s*\\b'  1:operator\nadd-highlighter shared/nix/code/ regex '[^-a-zA-Z0-9_](-+)' 1:operator\nadd-highlighter shared/nix/code/ regex '\\?'        0:operator\nadd-highlighter shared/nix/code/ regex '\\+\\+=?'    0:operator\nadd-highlighter shared/nix/code/ regex '(\\*|/|\\+)' 0:operator\nadd-highlighter shared/nix/code/ regex '!'         0:operator\nadd-highlighter shared/nix/code/ regex '//=?'      0:operator\nadd-highlighter shared/nix/code/ regex '[<>]=?\\??' 0:operator\nadd-highlighter shared/nix/code/ regex '(==|!=)'   0:operator\nadd-highlighter shared/nix/code/ regex '(&&|\\|\\|)' 0:operator\nadd-highlighter shared/nix/code/ regex '->'        0:operator\nadd-highlighter shared/nix/code/ regex \\bor\\b      0:operator\n\n# override any operators matched before\n# path:\nadd-highlighter shared/nix/code/ regex '\\s\\(*(\\.?\\.?[-A-Za-z0-9/_+.]*/[-A-Za-z0-9/_+.]*)[;?]?' 1:meta\n# imported path:\nadd-highlighter shared/nix/code/ regex <[-A-Za-z0-9/_+.]+> 0:meta\n# RFC 2396 URIs can be used without quoting. Strangely, \"string\" ends URL but ''indented'' one doesn't\n# List of prohibited characters was tested manually in nix-repl as it is not properly documented.\nadd-highlighter shared/nix/code/ regex '([^:/?#\\s]+):([^#(){}\\[\\]\";`|\\s\\\\]+)' 0:string\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden nix-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden nix-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %/ execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m s \\A|.\\z <ret> 1<a-&> /\n    >\n>\n\ndefine-command -hidden nix-insert-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n    >\n>\n\ndefine-command -hidden nix-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : nix-trim-indent <ret> }\n        # indent after lines beginning / ending with opener token\n        try %_ execute-keys -draft k x <a-k> ^\\h*[[{]|[[{]$ <ret> j <a-gt> _\n        # deindent closer token(s) when after cursor\n        try %_ execute-keys -draft x <a-k> ^\\h*[}\\]] <ret> gh / [}\\]] <ret> m <a-S> 1<a-&> _\n    >\n>\n\n§\n"
  },
  {
    "path": "rc/filetype/ocaml.kak",
    "content": "# http://ocaml.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.(ml|mli|mll|mly)$ %{\n    set-option buffer filetype ocaml\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=ocaml %{\n    require-module ocaml\n    set-option window static_words %opt{ocaml_static_words}\n    hook window InsertChar -group ocaml-insert '\\*' ocaml-insert-closing-comment-bracket\n    hook window InsertChar \\n -group ocaml-insert ocaml-insert-on-new-line\n    hook window ModeChange pop:insert:.* -group ocaml-trim-indent ocaml-trim-indent\n}\n\nhook -group ocaml-highlight global WinSetOption filetype=ocaml %{\n    add-highlighter window/ocaml ref ocaml\n    alias window alt ocaml-alternative-file\n    hook -once -always window WinSetOption filetype=.* %{\n        unalias window alt ocaml-alternative-file\n        remove-highlighter window/ocaml\n    }\n}\n\nprovide-module ocaml %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/ocaml regions\nadd-highlighter shared/ocaml/code default-region group\nadd-highlighter shared/ocaml/string region (?<!['\\\\])\" (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/ocaml/quotedstring region -match-capture %\"\\{(\\w*)\\|\" %\"\\|(\\w*)\\}\" fill string\nadd-highlighter shared/ocaml/comment region -recurse \\Q(* \\Q(* \\Q*) fill comment\n\nadd-highlighter shared/ocaml/code/char regex %{\\B'([^'\\\\]|(\\\\[\\\\\"'nrtb])|(\\\\\\d{3})|(\\\\x[a-fA-F0-9]{2})|(\\\\o[0-7]{3}))'\\B} 0:value\n\n# integer literals\nadd-highlighter shared/ocaml/code/ regex \\b[0-9][0-9_]*([lLn]?)\\b                          0:value\nadd-highlighter shared/ocaml/code/ regex \\b0[xX][A-Fa-f0-9][A-Fa-f0-9_]*([lLn]?)\\b         0:value\nadd-highlighter shared/ocaml/code/ regex \\b0[oO][0-7][0-7_]*([lLn]?)\\b                     0:value\nadd-highlighter shared/ocaml/code/ regex \\b0[bB][01][01_]*([lLn]?)\\b                       0:value\n# float literals\nadd-highlighter shared/ocaml/code/ regex \\b[0-9][0-9_]*(\\.[0-9_]*)?([eE][+-]?[0-9][0-9_]*)?                       0:value\nadd-highlighter shared/ocaml/code/ regex \\b0[xX][A-Fa-f0-9][A-Fa-f0-9]*(\\.[a-fA-F0-9_]*)?([pP][+-]?[0-9][0-9_]*)? 0:value\n# constructors. must be put before any module name highlighter, as a fallback for capitalized identifiers\nadd-highlighter shared/ocaml/code/ regex \\b[A-Z][a-zA-Z0-9_]*\\b                            0:value\n# the module name in a module path, e.g. 'M' in 'M.x'\nadd-highlighter shared/ocaml/code/ regex (\\b[A-Z][a-zA-Z0-9_]*[\\h\\n]*)(?=\\.)               0:module\n# (simple) module declarations\nadd-highlighter shared/ocaml/code/ regex (?<=module)([\\h\\n]+[A-Z][a-zA-Z0-9_]*)            0:module\n# (simple) signature declarations. 'type' is also highlighted, due to the lack of quantifiers in lookarounds.\n# Hence we must put keyword highlighters after this to recover proper highlight for 'type'\nadd-highlighter shared/ocaml/code/ regex (?<=module)([\\h\\n]+type[\\h\\n]+[A-Z][a-zA-Z0-9_]*) 0:module\n# (simple) open statements\nadd-highlighter shared/ocaml/code/ regex (?<=open)([\\h\\n]+[A-Z][a-zA-Z0-9_]*)              0:module\n# operators\nadd-highlighter shared/ocaml/code/ regex [@!$%%^&*\\-+=|<>/?]+                              0:operator\n\n\n# Macro\n# ‾‾‾‾‾\n\nevaluate-commands %sh{\n  keywords=\"and|as|asr|assert|begin|class|constraint|do|done|downto|else|end|exception|external|false\"\n  keywords=\"${keywords}|for|fun|function|functor|if|in|include|inherit|initializer|land|lazy|let|lor\"\n  keywords=\"${keywords}|lsl|lsr|lxor|match|method|mod|module|mutable|new|nonrec|object|of|open|or\"\n  keywords=\"${keywords}|private|rec|sig|struct|then|to|true|try|type|val|virtual|when|while|with\"\n\n  printf %s\\\\n \"declare-option str-list ocaml_static_words ${keywords}\" | tr '|' ' '\n\n# must be put at last, since we have highlighted some keywords ('type' in 'module type') to other things\n  printf %s \"\n    add-highlighter shared/ocaml/code/ regex \\b(${keywords})\\b 0:keyword\n  \"\n}\n\n# Conveniences\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\n# C has header and source files and you need to often switch between them.\n# Similarly OCaml has .ml (implementation) and .mli (interface files) and\n# one often needs to switch between them.\n#\n# This command provides a simple functionality that allows you to accomplish this.\ndefine-command ocaml-alternative-file -docstring 'Switch between .ml and .mli file or vice versa' %{\n    evaluate-commands %sh{\n        if [ \"${kak_buffile##*.}\" = 'ml' ]; then\n            printf \"edit -- '%s'\" \"$(printf %s \"${kak_buffile}i\" | sed \"s/'/''/g\")\"\n        elif [ \"${kak_buffile##*.}\" = 'mli' ]; then\n            printf \"edit -- '%s'\" \"$(printf %s \"${kak_buffile%i}\" | sed \"s/'/''/g\")\"\n        fi\n    }\n}\n\n}\n\n# Remove trailing whitespaces\ndefine-command -hidden ocaml-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        try %{ execute-keys -draft x s \\h+$ <ret> d }\n    }\n}\n\n# Preserve indentation when creating new line\ndefine-command -hidden ocaml-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy white spaces at the beginnig of the previous line\n        try %{ execute-keys -draft k x s ^\\h+ <ret> y jgh P x s \\h+$ <a-d> }\n        # increase indentation if the previous line ended with either '=' sign or do keyword\n        try %{ execute-keys -draft k x s (=|\\bdo)$ <ret> j <a-gt> }\n    }\n}\n\n# The OCaml comment is `(* Some comment *)`. Like the C-family this can be a multiline comment.\n#\n# Recognize when the user is trying to commence a comment when they type `(*` and\n# then automatically insert `*)` on behalf of the user. A small convenience.\ndefine-command -hidden ocaml-insert-closing-comment-bracket %{\n    try %{\n        execute-keys -draft 'HH<a-k>\\(\\*<ret>'\n        execute-keys '  *)<left><left><left>'\n    }\n}\n"
  },
  {
    "path": "rc/filetype/odin.kak",
    "content": "hook global BufCreate .*\\.odin %{\n    set-option buffer filetype odin\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=odin %{\n    require-module odin\n\n    set-option window static_words %opt{odin_static_words}\n\n    # cleanup trailing whitespaces when exiting insert mode\n    hook window ModeChange pop:insert:.* -group odin-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n    hook window InsertChar \\n -group odin-insert odin-insert-on-new-line\n    hook window InsertChar \\n -group odin-indent odin-indent-on-new-line\n    hook window InsertChar \\{ -group odin-indent odin-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group odin-indent odin-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window odin-.+ }\n}\n\nhook -group odin-highlight global WinSetOption filetype=odin %{\n    add-highlighter window/odin ref odin\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/odin }\n}\n\nprovide-module odin %§\n\nadd-highlighter shared/odin regions\nadd-highlighter shared/odin/code default-region group\nadd-highlighter shared/odin/string region %{(?<!')\"} %{(?<!\\\\)(\\\\\\\\)*\"} fill string\nadd-highlighter shared/odin/rawstring region ` ` fill string\nadd-highlighter shared/odin/character region %{'} %{(?<!\\\\)'} fill value\n\nadd-highlighter shared/odin/comment region -recurse /\\* /\\* \\*/ fill comment\nadd-highlighter shared/odin/inline_documentation region /// $ fill documentation\nadd-highlighter shared/odin/line_comment region // $ fill comment\n\nadd-highlighter shared/odin/code/ regex \"(?<!\\w)@\\w+\\b\" 0:meta\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden odin-insert-on-new-line %[\n    # copy // comments prefix and following white spaces\n    try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K/{2,}\\h* <ret> y<c-o>P<esc> }\n]\n\ndefine-command -hidden odin-indent-on-new-line %<\n\tevaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # indent after lines ending with { or (\n        try %[ execute-keys -draft kx <a-k> [{(]\\h*$ <ret> j<a-gt> ]\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        # align to opening paren of previous line\n        try %{ execute-keys -draft [( <a-k> \\A\\([^\\n]+\\n[^\\n]*\\n?\\z <ret> s \\A\\(\\h*.|.\\z <ret> '<a-;>' & }\n        # indent after a switch's case/default statements\n        try %[ execute-keys -draft kx <a-k> ^\\h*(case|default).*:$ <ret> j<a-gt> ]\n        # indent after keywords\n        try %[ execute-keys -draft <semicolon><a-F>)MB <a-k> \\A(if|else|while|for|try|catch)\\h*\\(.*\\)\\h*\\n\\h*\\n?\\z <ret> s \\A|.\\z <ret> 1<a-&>1<a-,><a-gt> ]\n        # deindent closing brace(s) when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> ]\n    =\n>\n\ndefine-command -hidden odin-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden odin-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\nevaluate-commands %sh{\n    values='false true nil ---'\n    types='bool b8 b16 b32 b64\n           int  i8 i16 i32 i64 i128\n           uint u8 u16 u32 u64 u128 uintptr\n           i16le i32le i64le i128le u16le u32le u64le u128le\n           i16be i32be i64be i128be u16be u32be u64be u128be\n           f16 f32 f64\n           f16le f32le f64le\n           f16be f32be f64be\n           complex32 complex64 complex128\n           quaternion64 quaternion128 quaternion256\n           rune\n           string cstring\n           rawptr\n           typeid\n           any'\n    keywords='asm auto_cast bit_set break case cast context continue defer distinct do dynamic else enum\n              fallthrough for foreign if import in map not_in or_else or_return package proc return struct\n              switch transmute typeid union using when where'\n    attributes=''\n    # ---------------------------------------------------------------------------------------------- #\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n    # ---------------------------------------------------------------------------------------------- #\n    add_highlighter() { printf \"add-highlighter shared/odin/code/ regex %s %s\\n\" \"$1\" \"$2\"; }\n    # ---------------------------------------------------------------------------------------------- #\n    add_word_highlighter() {\n      while [ $# -gt 0 ]; do\n          words=$1 face=$2; shift 2\n          regex=\"\\\\b($(join \"${words}\" '|'))\\\\b\"\n          add_highlighter \"$regex\" \"1:$face\"\n      done\n    }\n    # ---------------------------------------------------------------------------------------------- #\n    printf %s\\\\n \"declare-option str-list odin_static_words $(join \"${values} ${types} ${keywords} ${attributes} ${modules}\" ' ')\"\n    # ---------------------------------------------------------------------------------------------- #\n    add_word_highlighter \"$values\" \"value\" \"$types\" \"type\" \"$keywords\" \"keyword\" \"$attributes\" \"attribute\"\n    # ---------------------------------------------------------------------------------------------- #\n}\n\n§\n"
  },
  {
    "path": "rc/filetype/pascal.kak",
    "content": "# https://www.freepascal.org/docs-html/ref/ref.html\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection, see https://wiki.freepascal.org/file_types\nhook global BufCreate .*\\.(p|pp|pas|pascal)$ %{\n    set-option buffer filetype pascal\n}\nhook global BufCreate .*\\.(dpr|dpk|dfm)$ %{\n    set-option buffer filetype delphi\n}\nhook global BufCreate .*\\.(lpr|lfm)$ %{\n    set-option buffer filetype freepascal\n}\n\nhook global WinSetOption filetype=((free|object)?pascal|delphi) %[\n    require-module pascal\n    hook window ModeChange pop:insert:.* -group pascal-trim-indent pascal-trim-indent\n    hook window InsertChar \\n -group pascal-indent pascal-indent-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window pascal-.+ }\n    set-option window static_words %opt{static_words}\n]\n\nhook -group pascal-highlight global WinSetOption filetype=((free|object)?pascal|delphi) %[\n    add-highlighter window/pascal ref pascal\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/pascal }\n]\n\nprovide-module pascal %§\n\nadd-highlighter shared/pascal regions\nadd-highlighter shared/pascal/code default-region group\n\nevaluate-commands %sh¶\n    # This might seem like a lot of effort to highlight routines and\n    # properties correctly but it is worth it\n\n    id='([_a-zA-Z][\\w]{,126})\\s*' # identifier for variables etc.\n    id2=\"(?:$id\\.)?$id(?:<.*?>)?\" # (module|type).id\n    id4=\"(?:$id\\.)?(?:$id\\.)?(?:$id\\.)?$id\" # type.type.type.id\n    type=\":\\s*(specialize\\s+)?(?:(array\\s+of\\s+)?$id2)\" # 1:attribute 2:keyword 3:module 4:type\n\n    cat <<EOF\n        # routine without parameters\n        add-highlighter shared/pascal/code/simple_routine regex \\\n            \"\\b(?i)(function|procedure|constructor|destructor|operator)\\s+$id4(?:$type)?\" \\\n            2:type 3:type 4:type 5:function 6:attribute 7:keyword 8:module 9:type\n        add-highlighter shared/pascal/simple_property region \\\n            \"\\b(?i)property\\s+$id2:\"  \";\" group\n\n        # routine with parameters\n        add-highlighter shared/pascal/routine region \\\n            \"\\b(?i)(function|procedure|constructor|destructor|operator)(\\s+$id4)?\\s*(<.*?>)?\\s*\\(\"  \"\\).*?;\" regions\n        add-highlighter shared/pascal/property region \\\n            \"\\b(?i)property\\s+$id4\\[\"  \"\\].*?;\" regions\n\n        add-highlighter shared/pascal/routine/parameters  region -recurse \\( \\( \\) regions\n        add-highlighter shared/pascal/property/parameters region -recurse \\[ \\[ \\] regions\nEOF\n\n    # Used to highlight \"var1, var2, var3, var4 : type\" declarations\n    x=\"(?:$id,\\s*)?\"\n\n    for r in property routine; do\n        cat <<EOF\n            add-highlighter shared/pascal/$r/parameters/default default-region group\n            add-highlighter shared/pascal/$r/parameters/default/ regex \\\n                \"(?i)(?:(constref|const|var|out|univ)\\s+)?$x$x$x$x$x$id(?:$type)?\" \\\n                1:attribute 2:variable 3:variable 4:variable 5:variable 6:variable 7:variable 8:attribute 9:keyword 10:module 11:type\n            add-highlighter shared/pascal/$r/default default-region group\nEOF\n    done\n\n    cat <<EOF\n        add-highlighter shared/pascal/routine/default/ regex \\\n            \"\\b(?i)(function|procedure|constructor|destructor|operator)(?:\\s+$id4)?\" \\\n            1:keyword 2:type 3:type 4:type 5:function\n        add-highlighter shared/pascal/routine/default/return_type regex \\\n            \"(?i)$type\" 1:attribute 2:keyword 3:module 4:type\n        add-highlighter shared/pascal/routine/default/ regex \\\n            \"(?i)(of\\s+object|is\\s+nested)\" 1:keyword\nEOF\n\n    for r in property/default simple_property; do\n        cat <<EOF\n            add-highlighter shared/pascal/$r/ regex \"\\b(?i)(property)\" 1:keyword\n            add-highlighter shared/pascal/$r/type regex \":\\s*$id\" 1:type\n\n            # https://www.freepascal.org/docs-html/ref/refsu33.html\n            add-highlighter shared/pascal/$r/specifier regex \\\n                \"\\b(?i)(index|read|write|implements|(no)?default|stored)\\b\" 0:attribute\nEOF\n    done\n\n    for r in pascal pascal/routine pascal/routine/parameters pascal/property; do\n        cat <<EOF\n            # Example string: 'You''re welcome!'\n            add-highlighter shared/$r/string region \\\n                -recurse %{(?<!')('')+(?!')} %{'} %{'(?!')|\\$} group\n            add-highlighter shared/$r/string/ fill string\n            add-highlighter shared/$r/string/escape regex \"''\" 0:+b\n\n            add-highlighter shared/$r/directive region \\{\\\\$[a-zA-Z] \\} fill meta\n\n            # comments (https://www.freepascal.org/docs-html/ref/refse2.html)\n            add-highlighter shared/$r/comment_old region -recurse \\(\\* \\(\\* \\*\\) fill comment\n            add-highlighter shared/$r/comment_new region -recurse \\{ \\{  \\} fill comment\n            add-highlighter shared/$r/comment_oneline region // $ fill comment\nEOF\n    done\n\n\n    # The types \"string\" and \"file\", the value \"nil\", and the modifiers\n    # \"bitpacked\" and \"packed\" are not included.\n    reserved='and array as asm begin case class const constructor cppclass\n              destructor dispinterface div do downto else end except exports\n              finalization finally for function goto if implementation in\n              inherited initialization interface is label library mod not\n              object of operator or otherwise procedure program property raise\n              record repeat resourcestring set shl shr then threadvar to try\n              type unit until uses var while with xor'\n\n    # These are not reserved words in Free Pascal, but for consistency\n    # with other programming languages are highlighted as reserved words:\n    keywords='Break Continue Exit at on package'\n    # \"at\" is used in Delphi, e.g. 'raise object at address'\n\n    # https://www.freepascal.org/docs-html/ref/refsu3.html and\n    # https://wiki.freepascal.org/Reserved_words\n    # \"name\" and \"alias\" are not included beacuse they produce too many\n    # false positives. Some modifiers like \"index\" are only highlighted in\n    # certain places.\n    modifiers='absolute abstract assembler automated bitpacked cdecl contains\n               cppdecl cvar default deprecated dispid dynamic enumerator\n               experimental export external far far16 final forward generic\n               helper inline interrupt iocheck local near noreturn\n               nostackframe oldfpccall overload override packed pascal\n               platform private protected public published readonly register\n               reintroduce requires safecall saveregisters softfloat specialize\n               static stdcall strict syscall unaligned unimplemented varargs\n               virtual winapi writeonly'\n\n    # https://wiki.freepascal.org/Category:Data_types\n    # not highlighted for consistency with not built-in types\n    types='AnsiChar AnsiString Boolean Boolean16 Boolean32 Boolean64 Buffer\n           Byte ByteBool Cardinal Char Comp Currency Double DWord Extended\n           Int16 Int32 Int64 Int8 Integer IUnknown LongBool LongInt Longword\n           NativeInt NativeUInt OleVariant PAnsiChar PAnsiString PBoolean PByte\n           PByteArray PCardinal PChar PComp PCurrency PDate PDateTime PDouble\n           PDWord PExtended PHandle PInt64 PInteger PLongInt PLongWord Pointer\n           PPointer PQWord PShortInt PShortString PSingle PSmallInt PString\n           PUnicodeChar PUnicodeString PVariant PWideChar PWideString PWord\n           PWordArray PWordBool QWord QWordBool RawBytestring Real Real48\n           ShortInt ShortString Single SmallInt TClass TDate TDateTime Text\n           TextFile THandle TObject TTime UInt16 UInt32 UInt8 UnicodeChar\n           UnicodeString UTF16String UTF8String Variant WideChar WideString\n           Word WordBool file string'\n\n    constants='False True nil MaxInt Self'\n\n    # Add the language's grammar to the static completion list\n    echo declare-option str-list static_words $reserved $keywords $modifiers \\\n        $types $constants name index result constref out read write implements \\\n        nodefault stored message\n\n    # Replace spaces with a pipe\n    join() { eval set -- $1; IFS='|'; echo \"$*\"; }\n\n    cat <<EOF\n        add-highlighter shared/pascal/code/keywords  regex \\\n            (?i)(?<!&)\\b($(join \"$reserved $keywords\")|file\\s+of)\\b 0:keyword\n        add-highlighter shared/pascal/code/modifiers regex \\\n            (?i)(?<!\\.)\\b($(join \"$modifiers\"))\\b(?!\\()|message\\s+(?!:) 0:attribute\n        add-highlighter shared/pascal/code/index regex \\\n            '\\b(?i)(index)\\s+\\w+\\s*;' 1:attribute\nEOF\n\n    for r in code routine/parameters/default routine/default property/default simple_property; do\n        cat <<EOF\n            add-highlighter shared/pascal/$r/ regex '[.:=<>@^*/+-]' 0:operator\n            add-highlighter shared/pascal/$r/constants regex \\\n                \\b(?i)($(join \"$constants\"))\\b 0:value\n\n            # numbers (https://www.freepascal.org/docs-html/ref/refse6.html)\n            add-highlighter shared/pascal/$r/decimal regex \\b\\d+([eE][+-]?\\d+)?\\b 0:value\n            add-highlighter shared/pascal/$r/hex     regex \\\\\\$[\\da-fA-F]+\\b 0:value\n            add-highlighter shared/pascal/$r/octal   regex &[0-7]+\\b 0:value\n            add-highlighter shared/pascal/$r/binary  regex \\%[01]+\\b 0:value\n            add-highlighter shared/pascal/$r/char    regex '#\\d+\\b' 0:value\nEOF\n    done\n¶\n\ndefine-command -hidden pascal-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden pascal-indent-on-new-line %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after certain keywords\n        try %{ execute-keys -draft kx<a-k>(?i)(asm|begin|const|else|except|exports|finalization|finally|label|of|otherwise|private|property|public|protected|published|record|repeat|resourcestring|threadvar|try|type|uses|var|:)\\h*$<ret>j<a-gt> }\n    }\n}\n§\n\n# Other syntax highlighters for reference:\n# https://github.com/pygments/pygments/blob/master/pygments/lexers/pascal.py\n# https://github.com/codemirror/CodeMirror/blob/master/mode/pascal/pascal.js\n# https://github.com/vim/vim/blob/master/runtime/syntax/pascal.vim\n# https://github.com/highlightjs/highlight.js/blob/master/src/languages/delphi.js\n"
  },
  {
    "path": "rc/filetype/perf.kak",
    "content": "provide-module perf-report %{\n    add-highlighter shared/perf-report group\n    add-highlighter shared/perf-report/above_threshold regex '\\b([5-9]|\\d{2})\\.\\d+%' 0:red\n    add-highlighter shared/perf-report/below_threshold regex '\\b[0-4]\\.\\d+%' 0:green\n\n\n    define-command -override perf-report-focus %{\n        execute-keys 'xs...\\d+\\.\\d+%<ret><a-:><a-semicolon>vtv<lt><semicolon>'\n    }\n}\n\nhook -group perf-report-highlight global WinSetOption filetype=perf-report %{\n    require-module perf-report\n    add-highlighter window/perf-report ref perf-report\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/perf-report }\n\n    map window normal <ret> ': perf-report-focus<ret>'\n}\n\nprovide-module perf-annotate %{\n    require-module gas\n    add-highlighter shared/perf-annotate group\n    add-highlighter shared/perf-annotate/gas ref gas\n    add-highlighter shared/perf-annotate/above_threshold regex '^\\h+([1-9]|\\d{2})\\.\\d+\\b' 0:red\n    add-highlighter shared/perf-annotate/below_threshold regex '^\\h+0\\.\\d+\\b' 0:green\n}\n\nhook -group perf-annotate-highlight global WinSetOption filetype=perf-annotate %{\n    require-module perf-annotate\n    add-highlighter window/perf-annotate ref perf-annotate\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/perf-annotate }\n}\n"
  },
  {
    "path": "rc/filetype/perl.kak",
    "content": "# https://www.perl.org/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.(t|p[lm])$ %{\n    set-option buffer filetype perl\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=perl %{\n    require-module perl\n\n    set-option window static_words %opt{perl_static_words}\n\n    # cleanup trailing whitespaces when exiting insert mode\n    hook window ModeChange pop:insert:.* -group perl-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n    hook window InsertChar \\n -group perl-insert perl-insert-on-new-line\n    hook window InsertChar \\n -group perl-indent perl-indent-on-new-line\n    hook window InsertChar \\{ -group perl-indent perl-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group perl-indent perl-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window perl-.+ }\n}\n\nhook -group perl-highlight global WinSetOption filetype=perl %{\n    add-highlighter window/perl ref perl\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/perl }\n}\n\nprovide-module perl %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/perl regions\nadd-highlighter shared/perl/code default-region group\nadd-highlighter shared/perl/command        region (?<!\\$)(?<!\\\\)`   (?<!\\\\)(\\\\\\\\)*` fill meta\nadd-highlighter shared/perl/double_string  region (?<!\\$)\"          (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/perl/single_string  region (?<!\\$)(?<!\\\\\\\\)' (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/perl/comment        region (?<!\\$)(?<!\\\\)#   $               fill comment\n\nadd-highlighter shared/perl/regex          region m?(?<!/)(?<!qr)/[^/\\n]+(?=/)  /\\w*   fill meta\nadd-highlighter shared/perl/sregex         region s/[^/\\n]+/[^/\\n]+(?=/)        /\\w*   fill meta\nadd-highlighter shared/perl/bregex         region s\\{[^\\}\\n]+\\}\\{[^\\}\\n]*(?=\\}) \\}\\w*  fill meta\n\nadd-highlighter shared/perl/quote_brace    region -recurse \\{ \\bq[qrwx]?\\{ \\}          fill string\nadd-highlighter shared/perl/quote_paren    region -recurse \\( \\bq[qrwx]?\\( \\)          fill string\nadd-highlighter shared/perl/quote_brack    region -recurse \\[ \\bq[qrwx]?\\[ \\]          fill string\nadd-highlighter shared/perl/quote_angle    region -recurse  < \\bq[qrwx]?<   >          fill string\nadd-highlighter shared/perl/quote_punct    region -match-capture '\\bq[qwx]?([:;!@#$%^&*|,.?/~=+-])' '(.)' fill string\nadd-highlighter shared/perl/quote_regex    region -match-capture      '\\bqr([:;!@#$%^&*|,.?/~=+-])' '(.)' fill meta\n\nadd-highlighter shared/perl/double_heredoc region -match-capture <<~?\\h*'(\\w*)' ^\\t*(\\w*)$ fill string\nadd-highlighter shared/perl/single_heredoc region -match-capture <<~?\\h*\"(\\w*)\" ^\\t*(\\w*)$ fill string\nadd-highlighter shared/perl/bare_heredoc   region -match-capture <<~?(\\w+)      ^\\t*(\\w+)$ fill string\nadd-highlighter shared/perl/pod            region ^=\\w+  ^=cut\\b                            fill string\n\nevaluate-commands %sh{\n    # Grammar\n    keywords=\"else lock qw elsif lt qx eq exp ne sub for no my not tr goto and foreach or break exit unless cmp ge package until continue gt while if qq xor do le qr return\"\n    attributes=\"END AUTOLOAD BEGIN CHECK UNITCHECK INIT DESTROY\n                length setpgrp endgrent link setpriority endhostent listen setprotoent endnetent local setpwent\n                endprotoent localtime setservent endpwent log setsockopt endservent lstat shift eof map shmctl eval mkdir shmget exec msgctl shmread\n                exists msgget shmwrite msgrcv shutdown fcntl msgsnd sin fileno sleep flock next socket fork socketpair format oct sort\n                formline open splice getc opendir split getgrent ord sprintf getgrgid our sqrt getgrnam pack srand gethostbyaddr pipe stat gethostbyname\n                pop state gethostent pos study getlogin print substr getnetbyaddr printf symlink abs getnetbyname prototype syscall accept getnetent\n                push sysopen alarm getpeername quotemeta sysread atan2 getpgrp rand sysseek getppid read system getpriority readdir syswrite bind\n                getprotobyname readline tell binmode getprotobynumber readlink telldir bless getprotoent readpipe tie getpwent recv tied caller\n                getpwnam redo time chdir getpwuid ref times getservbyname rename truncate chmod getservbyport require uc chomp getservent reset ucfirst\n                chop getsockname umask chown getsockopt reverse undef chr glob rewinddir chroot gmtime rindex unlink close rmdir unpack\n                closedir grep say unshift connect hex scalar untie cos index seek use crypt seekdir utime dbmclose int select values dbmopen ioctl semctl\n                vec defined join semget wait delete keys semop waitpid kill send wantarray die last setgrent warn dump lc sethostent write each lcfirst setnetent\"\n    values=\"ARGV STDERR STDOUT ARGVOUT STDIN __DATA__ __END__ __FILE__ __LINE__ __PACKAGE__\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list perl_static_words $(join \"${keywords} ${attributes} ${values}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/perl/code/ regex \\b($(join \"${keywords}\" '|'))\\b 0:keyword\n        add-highlighter shared/perl/code/ regex \\b($(join \"${attributes}\" '|'))\\b 0:attribute\n        add-highlighter shared/perl/code/ regex \\b($(join \"${values}\" '|'))\\b 0:value\n    \"\n}\n\nadd-highlighter shared/perl/code/ regex (?!\\$)-?([0-9]*\\.(?!0[xXbB]))?\\b([0-9]+|0[xX][0-9a-fA-F]+|0[bb][01_]+)\\.?([eE][+-]?[0-9]+)?i?\\b 0:value\nadd-highlighter shared/perl/code/ regex %{\\$!|\\$\"|\\$#|\\$\\$|\\$%|\\$&|\\$'|\\$\\(|\\$\\)|\\$\\*|\\$\\+|\\$,|\\$_|\\$-|\\$`|\\$\\.|\\$/|\\$:|\\$;|\\$<|\\$=|\\$>|\\$\\?|\\$@|\\$\\[|\\$\\\\|\\$\\]|\\$\\^|\\$\\||\\$~|%!|@\\+|@-|@_} 0:value\nadd-highlighter shared/perl/code/ regex (%ENV|%INC|%OVERLOAD|%SIG|@ARGV|@INC|@LAST_MATCH_START) 0:value\nadd-highlighter shared/perl/code/ regex %{%\\^(H)\\b} 0:value\nadd-highlighter shared/perl/code/ regex \\$\\^(S|T|V|W|X|A|C|D|E|F|H|I|L|M|N|O|P|R)\\b 0:value\nadd-highlighter shared/perl/code/ regex \\$\\^(RE_TRIE_MAXBUF|TAINT|UNICODE|UTF8LOCALE|WARNING_BITS|WIDE_SYSTEM_CALLS|CHILD_ERROR_NATIVE|ENCODING|OPEN|RE_DEBUG_FLAGS)\\b 0:value\n\nadd-highlighter shared/perl/code/ regex \\$[0-9]+ 0:attribute\nadd-highlighter shared/perl/code/ regex \\b-(B|b|C|c|d|e|f|g|k|l|M|O|o|p|r|R|S|s|T|t|u|w|W|X|x|z)\\b 0:attribute\n\nadd-highlighter shared/perl/code/ regex \\$[a-zA-Z_][a-zA-Z0-9_]* 0:variable\n\nadd-highlighter shared/perl/code/ regex \\$(a|b|LAST_REGEXP_CODE_RESULT|LIST_SEPARATOR|MATCH|MULTILINE_MATCHING|NR|OFMT|OFS|ORS|OS_ERROR|OSNAME|OUTPUT_AUTO_FLUSH|OUTPUT_FIELD_SEPARATOR|OUTPUT_RECORD_SEPARATOR)\\b 0:value\nadd-highlighter shared/perl/code/ regex \\$(LAST_REGEXP_CODE_RESULT|LIST_SEPARATOR|MATCH|MULTILINE_MATCHING|NR|OFMT|OFS|ORS|OS_ERROR|OSNAME|OUTPUT_AUTO_FLUSH|OUTPUT_FIELD_SEPARATOR|OUTPUT_RECORD_SEPARATOR|PERL_VERSION|ACCUMULATOR|PERLDB|ARG|PID|ARGV|POSTMATCH|PREMATCH|BASETIME|PROCESS_ID|CHILD_ERROR|PROGRAM_NAME|COMPILING|REAL_GROUP_ID|DEBUGGING|REAL_USER_ID|EFFECTIVE_GROUP_ID|RS|EFFECTIVE_USER_ID|SUBSCRIPT_SEPARATOR|EGID|SUBSEP|ERRNO|SYSTEM_FD_MAX|EUID|UID|EVAL_ERROR|WARNING|EXCEPTIONS_BEING_CAUGHT|EXECUTABLE_NAME|EXTENDED_OS_ERROR|FORMAT_FORMFEED|FORMAT_LINE_BREAK_CHARACTERS|FORMAT_LINES_LEFT|FORMAT_LINES_PER_PAGE|FORMAT_NAME|FORMAT_PAGE_NUMBER|FORMAT_TOP_NAME|GID|INPLACE_EDIT|INPUT_LINE_NUMBER|INPUT_RECORD_SEPARATOR|LAST_MATCH_END|LAST_PAREN_MATCH)\\b 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden perl-insert-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # copy # comments prefix and following white spaces\n        try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K#\\h* <ret> y<c-o>P<esc> }\n    =\n~\n\ndefine-command -hidden perl-indent-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # indent after lines ending with { or (\n        try %[ execute-keys -draft kx <a-k> [{(]\\h*$ <ret> j<a-gt> ]\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        # align to opening paren of previous line\n        try %{ execute-keys -draft [( <a-k> \\A\\([^\\n]+\\n[^\\n]*\\n?\\z <ret> s \\A\\(\\h*.|.\\z <ret> '<a-;>' & }\n        # indent after a switch's case/default statements\n        try %[ execute-keys -draft kx <a-k> ^\\h*(case|default).*:$ <ret> j<a-gt> ]\n        # indent after if|else|while|for\n        try %[ execute-keys -draft <semicolon><a-F>)MB <a-k> \\A(if|else|while|for)\\h*\\(.*\\)\\h*\\n\\h*\\n?\\z <ret> s \\A|.\\z <ret> 1<a-&>1<a-,><a-gt> ]\n        # deindent closing brace(s) when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> ]\n    =\n~\n\ndefine-command -hidden perl-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{) <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden perl-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\n§\n"
  },
  {
    "path": "rc/filetype/php.kak",
    "content": "# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](phpt?) %{\n    set-option buffer filetype php\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=php %{\n    require-module php\n\n    hook window ModeChange pop:insert:.* -group php-trim-indent php-trim-indent\n    hook window InsertChar .* -group php-indent php-indent-on-char\n    hook window InsertChar \\n -group php-insert php-insert-on-new-line\n    hook window InsertChar \\n -group php-indent php-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window php-.+ }\n}\n\nhook -group php-highlight global WinSetOption filetype=php %{\n    add-highlighter window/php-file ref php-file\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/php-file }\n}\n\nprovide-module php %§\nrequire-module html\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/php regions\nadd-highlighter shared/php/code  default-region group\nadd-highlighter shared/php/double_string region '\"'    (?<!\\\\)(\\\\\\\\)*\" group\nadd-highlighter shared/php/single_string region \"'\"    (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/php/doc_comment   region ///    '$'             group\nadd-highlighter shared/php/doc_comment2  region /\\*\\*  \\*/             ref php/doc_comment\nadd-highlighter shared/php/comment1      region //     '$'             fill comment\nadd-highlighter shared/php/comment2      region /\\*    \\*/             fill comment\nadd-highlighter shared/php/comment3      region '#'    '$'             fill comment\nadd-highlighter shared/php/heredoc       region -match-capture '<<<''?(.*?)''?$' '^\\h*(.*?);?$' fill string\n\n\nadd-highlighter shared/php/code/ regex &?\\$\\w* 0:variable\nadd-highlighter shared/php/code/ regex \\b(false|null|parent|self|this|true)\\b 0:value\nadd-highlighter shared/php/code/ regex \"(\\b|-)[0-9]*\\.?[0-9]+\\b\" 0:value\nadd-highlighter shared/php/code/ regex \\b((string|int|bool)|[A-Z][a-z].*?)\\b 0:type\nadd-highlighter shared/php/code/ regex \\B/[^\\n/]+/[gimy]* 0:meta\nadd-highlighter shared/php/code/ regex '<\\?(php)?|\\?>' 0:meta\n\nadd-highlighter shared/php/double_string/ fill string\nadd-highlighter shared/php/double_string/ regex (?<!\\\\)(\\\\\\\\)*(\\$\\w+)(->\\w+)* 0:variable\nadd-highlighter shared/php/double_string/ regex \\{(?<!\\\\)(\\\\\\\\)*(\\$\\w+)(->\\w+)*\\} 0:variable\n\n# Highlight doc comments\nadd-highlighter shared/php/doc_comment/ fill string\nadd-highlighter shared/php/doc_comment/ regex '`.*`' 0:module\nadd-highlighter shared/php/doc_comment/ regex '@\\w+' 0:meta\n\n# Keywords are collected at\n# http://php.net/manual/en/reserved.keywords.php\nadd-highlighter shared/php/code/ regex \\b(__halt_compiler|abstract|and|array|as|break|callable|case|catch|class|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|eval|exit|extends|final|finally|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|namespace|new|or|print|private|protected|public|require|require_once|return|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__CLASS__|__DIR__|__FILE__|__FUNCTION__|__LINE__|__METHOD__|__NAMESPACE__|__TRAIT__)\\b 0:keyword\n\n# Highlighter for html with php tags in it, i.e. the structure of conventional php files\nadd-highlighter shared/php-file regions\nadd-highlighter shared/php-file/html default-region ref html\nadd-highlighter shared/php-file/php  region '<\\?(php)?'     '\\?>'      ref php\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden php-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden php-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %/ execute-keys -draft <a-h> <a-k> ^\\h+[\\]}]$ <ret> m s \\A|.\\z <ret> 1<a-&> /\n    >\n>\n\ndefine-command -hidden php-insert-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # copy // comments or docblock * prefix and following white spaces\n        try %{ execute-keys -draft s [^/] <ret> k x s ^\\h*\\K(?://|[*][^/])\\h* <ret> y gh j P }\n        # append \" * \" on lines starting a multiline /** or /* comment\n        try %{ execute-keys -draft k x s ^\\h*/[*][* ]? <ret> j gi i <space>*<space> }\n    >\n>\n\ndefine-command -hidden php-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : php-trim-indent <ret> }\n        # indent after lines beginning / ending with opener token\n        try %_ execute-keys -draft k x <a-k> ^\\h*[[{]|[[{]$ <ret> j <a-gt> _\n        # deindent closer token(s) when after cursor\n        try %_ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> _\n    >\n>\n\n§\n"
  },
  {
    "path": "rc/filetype/pony.kak",
    "content": "# http://ponylang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](pony) %{\n    set-option buffer filetype pony\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=pony %{\n    require-module pony\n\n    set-option window static_words %opt{pony_static_words}\n\n    # cleanup trailing whitespaces on current line insert end\n    hook window ModeChange pop:insert:.* -group pony-trim-indent %{ try %{ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d } }\n    hook window InsertChar \\n -group pony-indent pony-indent-on-new-line\n    hook window InsertChar \\n -group pony-insert pony-insert-on-new-line\n\n    set-option buffer extra_word_chars '_' \"'\"\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window pony-.+ }\n}\n\nhook -group pony-highlight global WinSetOption filetype=pony %{\n    add-highlighter window/pony ref pony\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter pony }\n}\n\nprovide-module pony %§\n\n# Highlighters & Completion\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/pony regions\nadd-highlighter shared/pony/code default-region group\n\nadd-highlighter shared/pony/comment region '/\\*' '\\*/' fill comment\nadd-highlighter shared/pony/line_comment region '//' '$' fill comment\n\n# String literals\nadd-highlighter shared/pony/string region -match-capture '(\"\"\"|\")' '(?<!\\\\)(?:\\\\\\\\)*(\"\"\"|\")' group\nadd-highlighter shared/pony/string/ fill string\nadd-highlighter shared/pony/string/ regex '(?:\\\\a|\\\\b|\\\\e|\\\\f|\\\\n|\\\\r|\\\\t||\\\\v|\\\\\\\\|\\\\''|\\\\0|\\\\x[0-9a-fA-F]{2}|\\\\u[0-9a-fA-F]{4}|\\\\u[0-9a-fA-F]{6})' 0:meta\n\n# Character literals\n# negative lookbehind to account for variables with prime such as myvar' or myvar''\nadd-highlighter shared/pony/character region \"(?<![a-z0-9'])'\" (?<!\\\\)(\\\\\\\\)*' group\nadd-highlighter shared/pony/character/ fill string\nadd-highlighter shared/pony/character/ regex '(?:\\\\a|\\\\b|\\\\e|\\\\f|\\\\n|\\\\r|\\\\t||\\\\v|\\\\\\\\|\\\\''|\\\\0|\\\\x[0-9a-fA-F]{2})' 0:meta\n\n# Operators\nadd-highlighter shared/pony/code/ regex '=|\\+|-|\\*|/|%|%%|<<|>>|==|!=|<|<=|>=|>|=>|;' 0:operator\nadd-highlighter shared/pony/code/ regex '(\\+|-|\\*|/|%|%%|<<|>>|==|!=|<|<=|>=|>)~' 0:operator\nadd-highlighter shared/pony/code/ regex '(\\+|-|\\*|/|%|%%)\\?' 0:operator\nadd-highlighter shared/pony/code/ regex '^\\h*|' 0:operator\nadd-highlighter shared/pony/code/ regex '\\b_\\b' 0:operator\n\n# Integer literals\nadd-highlighter shared/pony/code/ regex '\\b[0-9](_?[0-9])*\\b' 0:value\nadd-highlighter shared/pony/code/ regex '\\b0x[0-9a-fA-F](_?[0-9a-fA-F])*\\b' 0:value\nadd-highlighter shared/pony/code/ regex '\\b0b[01](_?[01])*\\b' 0:value\n\n# Float literals\nadd-highlighter shared/pony/code/ regex '\\b[0-9]+\\.[0-9]+(?:[eE][-+]?[0-9]+)?\\b' 0:value\nadd-highlighter shared/pony/code/ regex '\\b[0-9]+[eE][-+]?[0-9]+\\b' 0:value\n\n# Type literals\nadd-highlighter shared/pony/code/ regex '\\b_?[A-Z][A-Za-z0-9]*\\b' 0:type\n\n# Literal words are highlighted below to also allow for autocompletion by appending\n# them to static_words.\nevaluate-commands %sh{\n    # Grammar\n    values=\"true|false|this\"\n    meta='use'\n    # Keyword list is collected using `keyword.kwlist` from `keyword`\n    keywords=\"as|break|match|continue|else|elseif|try|in|return|end|for|is\"\n    keywords=\"${keywords}|recover|consume|error|do|then|if|while|where|with\"\n    keywords=\"${keywords}|class|struct|object|actor|interface|trait|primitive\"\n    keywords=\"${keywords}|type|var|let|embed|repeat|until|addressof\"\n    func_decl=\"new|fun|be\"\n    capabilities=\"iso|ref|box|tag|val|trn\"\n    operators=\"and|or|xor|not\"\n    builtin_types=\"String|None|Any|Array\"\n\n    # Add the language's grammar to the static completion list\n    static_words=\"${values} ${meta} ${keywords} ${types_decl} ${capabilities}\"\n    static_words=\"${static_words} ${operators} ${builtin_types}\"\n    printf %s\\\\n \"declare-option str-list pony_static_words ${static_words}\" | tr '|' ' '\n\n    # Highlight keywords\n    # The (?!') ensures that keywords with prime are highlighted as usual, for example try'\n    printf %s \"\n        add-highlighter shared/pony/code/ regex \\b(${values})\\b(?!') 0:value\n        add-highlighter shared/pony/code/ regex \\b(${operators})\\b(?!') 0:operator\n        add-highlighter shared/pony/code/ regex \\b(${meta})\\b(?!') 0:meta\n        add-highlighter shared/pony/code/ regex \\b(${func_decl})(\\s+(${capabilities}))?(\\s+\\w+)[\\[(] 1:keyword 3:attribute 4:function\n        add-highlighter shared/pony/code/ regex \\b(${func_decl})\\b(?!') 0:keyword\n        add-highlighter shared/pony/code/ regex \\b(${keywords})\\b(?!') 0:keyword\n        add-highlighter shared/pony/code/ regex \\b(${capabilities})\\b(?!')(!|\\\\^)? 1:attribute 2:attribute\n    \"\n\n    # Highlight types and attributes\n    printf %s \"\n        add-highlighter shared/pony/code/ regex '@[\\w_]+\\b' 0:attribute\n    \"\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden pony-insert-on-new-line %<\n    evaluate-commands -no-hooks -draft -itersel %<\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K//\\h* <ret> y jgi P }\n\n        # wisely add `end` keyword\n        evaluate-commands -save-regs x %<\n            try %{ execute-keys -draft k x s ^ \\h + <ret> \\\" x y } catch %{ reg x '' }\n            try %<\n                evaluate-commands -draft %<\n                    # Check if previous line opens a block\n                    execute-keys -draft kx <a-k>^<c-r>x(try|match|recover|repeat|object|.+\\bdo$|.+\\bthen$)[^0-9A-Za-z_']<ret>\n                    # Check that we didn't put an end on the previous line that closes the block\n                    execute-keys -draft kx <a-K> \\bend$<ret>\n                    # Check that we do not already have an end for this indent level which is first set via `pony-indent-on-new-line` hook\n                    execute-keys -draft }i J x <a-K> ^<c-r>x(end|else|elseif|until)[^0-9A-Za-z_']<ret>\n                >\n                execute-keys -draft o<c-r>xend<esc> # insert a new line with containing end\n            >\n        >\n    >\n>\n\ndefine-command -hidden pony-indent-on-new-line %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft , K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after line ending with: =>, do, try, then, else, =; or\n        # starting with: actor, class, struct, trait, interface, if, elseif, recover, repeat\n        # excluding: primitive, type -- because they are often written in a single line\n        try %{ execute-keys -draft , k x <a-k> (\\b(?:do|try|then|else|recover|repeat)|=>|=)$ | ^\\h*(actor|class|struct|trait|interface|object)\\b <ret> j <a-gt> }\n        # else, end are always de-indented\n        try %{ execute-keys -draft , k x <a-k> \\b(else|end):$ <ret> k x s ^\\h* <ret> y j x <a-k> ^<c-r>\" <ret> J <a-lt> }\n    }\n}\n\n§\n"
  },
  {
    "path": "rc/filetype/prolog.kak",
    "content": "# Prolog\n# ----------------------\n\n# Adapted from rc/filetype/erlang.kak\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\nhook global BufCreate .*[.](pl|P) %{\n    set-option buffer filetype prolog\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\nhook global WinSetOption filetype=prolog %{\n    require-module prolog\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window prolog-.+ }\n}\n\nhook -group prolog-highlight global WinSetOption filetype=prolog %{\n    add-highlighter window/prolog ref prolog\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/prolog }\n}\n\nprovide-module prolog %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/prolog regions\nadd-highlighter shared/prolog/default default-region group\n\nadd-highlighter shared/prolog/comment region /\\* \\*/ fill comment\nadd-highlighter shared/prolog/comment_line region '%' $ fill comment\nadd-highlighter shared/prolog/attribute_atom_single_quoted region %{-'} %{(?<!\\\\)(?:\\\\\\\\)*'(?=[\\( \\.])} fill builtin\nadd-highlighter shared/prolog/attribute region '\\b-[a-z][\\w@]*(?=[\\( \\.])' '\\K' fill builtin\nadd-highlighter shared/prolog/atom_single_quoted region %{'} %{(?<!\\\\)(?:\\\\\\\\)*'} fill type\nadd-highlighter shared/prolog/char_list region %{\"} %{(?<!\\\\)(?:\\\\\\\\)*\"} fill string\n\n# default-region regex highlighters\nadd-highlighter shared/prolog/default/atom regex '\\b[a-z]\\w*\\b' 0:type\nadd-highlighter shared/prolog/default/pred_call regex '\\b[a-z]\\w*(?=\\()' 0:function\nadd-highlighter shared/prolog/default/keywords regex '\\b(div|rem|is)\\b' 0:keyword\nadd-highlighter shared/prolog/default/variable_name regex '\\b(?<!\\?)[A-Z_][\\w]*\\b' 0:variable\n\nadd-highlighter shared/prolog/default/base_number regex '\\b(\\d[_\\d]*(?<!_)#[a-zA-Z0-9][a-z_A-Z0-9]*(?<!_)(?!\\{))\\b' 1:value\nadd-highlighter shared/prolog/default/float regex '\\b(?<![\\.])(\\d[\\d_]*(?<!_)\\.\\d[\\d_]*(?<!_)(?:e[+-]?\\d[\\d_]*(?<!_))?)\\b' 1:value\nadd-highlighter shared/prolog/default/integer regex '\\b(?<!/)(\\d[\\d_]*)(?<!_)\\b' 1:value\n\n]\n"
  },
  {
    "path": "rc/filetype/protobuf.kak",
    "content": "# https://developers.google.com/protocol-buffers/\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.proto$ %{\n    set-option buffer filetype protobuf\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=protobuf %[\n    require-module protobuf\n\n    set-option window static_words %opt{protobuf_static_words}\n\n    hook window ModeChange pop:insert:.* -group protobuf-trim-indent protobuf-trim-indent\n    hook -group protobuf-insert window InsertChar \\n protobuf-insert-on-newline\n    hook -group protobuf-indent window InsertChar \\n protobuf-indent-on-newline\n    hook -group protobuf-indent window InsertChar \\{ protobuf-indent-on-opening-curly-brace\n    hook -group protobuf-indent window InsertChar \\} protobuf-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window protobuf-.+ }\n]\n\nhook -group protobuf-highlight global WinSetOption filetype=protobuf %{\n    add-highlighter window/protobuf ref protobuf\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/protobuf }\n}\n\nprovide-module protobuf %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/protobuf regions\nadd-highlighter shared/protobuf/code default-region group\nadd-highlighter shared/protobuf/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/protobuf/comment region /\\* \\*/ fill comment\nadd-highlighter shared/protobuf/comment_line region '//' $ fill comment\n\nadd-highlighter shared/protobuf/code/ regex %{(0x)?[0-9]+\\b} 0:value\n\nevaluate-commands %sh{\n    # Grammer\n    keywords='default deprecated enum extend import message oneof option\n              package service syntax'\n    attributes='optional repeated required'\n    types='double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64\n           sfixed32 sfixed64 bool string bytes rpc'\n    values='false true'\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammer to the static completion list\n    printf %s\\\\n \"declare-option str-list protobuf_static_words $(join \"${keywords} ${attributes} ${types} ${values}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/protobuf/code/keywords regex \\b($(join \"${keywords}\" '|'))\\b 0:keyword\n        add-highlighter shared/protobuf/code/attributes regex \\b($(join \"${attributes}\" '|'))\\b 0:attribute\n        add-highlighter shared/protobuf/code/types regex \\b($(join \"${types}\" '|'))\\b 0:type\n        add-highlighter shared/protobuf/code/values regex \\b($(join \"${values}\" '|'))\\b 0:value\n    \"\n}\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden protobuf-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden protobuf-insert-on-newline %{\n    evaluate-commands -draft -itersel %[\n        # copy // comments prefix\n        try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K/{2,}(\\h*(?=\\S))? <ret> y<c-o>P<esc> }\n    ]\n}\n\ndefine-command -hidden protobuf-indent-on-newline %~\n    evaluate-commands -draft -itersel %[\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # indent after lines ending with {\n        try %[ execute-keys -draft kx <a-k> \\{\\h*$ <ret> j<a-gt> ]\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        # deindent closing brace(s) when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> ]\n    ]\n~\n\ndefine-command -hidden protobuf-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden protobuf-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\n]\n"
  },
  {
    "path": "rc/filetype/prql.kak",
    "content": "# https://prql-lang.org/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](prql) %{\n    set-option buffer filetype prql\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=prql %{\n    require-module prql\n\n    set-option window static_words %opt{prql_static_words}\n\n    hook window InsertChar \\n -group prql-insert prql-insert-on-new-line\n    hook window InsertChar \\n -group prql-indent prql-indent-on-new-line\n    # cleanup trailing whitespaces on current line insert end\n    hook window ModeChange pop:insert:.* -group prql-trim-indent %{ try %{ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d } }\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window prql-.+ }\n}\n\nhook -group prql-highlight global WinSetOption filetype=prql %{\n    add-highlighter window/prql ref prql\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/prql }\n}\n\nprovide-module prql %§\n\n# Highlighters & Completion\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/prql regions\nadd-highlighter shared/prql/code default-region group\n\nadd-highlighter shared/prql/documentation region '##'  '$'              fill documentation\nadd-highlighter shared/prql/comment       region '#'   '$'              fill comment\n\n# String interpolation\nadd-highlighter shared/prql/f_triple_string region -match-capture f(\"\"\"|''') (?<!\\\\)(?:\\\\\\\\)*(\"\"\"|''') group\nadd-highlighter shared/prql/f_triple_string/ fill string\nadd-highlighter shared/prql/f_triple_string/ regex \\{.*?\\} 0:value\n\nadd-highlighter shared/prql/f_double_string region 'f\"'   (?<!\\\\)(\\\\\\\\)*\" group\nadd-highlighter shared/prql/f_double_string/ fill string\nadd-highlighter shared/prql/f_double_string/ regex \\{.*?\\} 0:value\n\nadd-highlighter shared/prql/f_single_string region \"f'\"   (?<!\\\\)(\\\\\\\\)*' group\nadd-highlighter shared/prql/f_single_string/ fill string\nadd-highlighter shared/prql/f_single_string/ regex \\{.*?\\} 0:value\n\n\n# Regular string\nadd-highlighter shared/prql/triple_string region -match-capture (\"\"\"|''') (?<!\\\\)(?:\\\\\\\\)*(\"\"\"|''') fill string\nadd-highlighter shared/prql/double_string region '\"'   (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/prql/single_string region \"'\"   (?<!\\\\)(\\\\\\\\)*' fill string\n\n# Integer formats\nadd-highlighter shared/prql/code/ regex '(?i)\\b0b[01]+l?\\b' 0:value\nadd-highlighter shared/prql/code/ regex '(?i)\\b0x[\\da-f]+l?\\b' 0:value\nadd-highlighter shared/prql/code/ regex '(?i)\\b0o?[0-7]+l?\\b' 0:value\nadd-highlighter shared/prql/code/ regex '(?i)\\b([1-9]\\d*|0)l?\\b' 0:value\n# Float formats\nadd-highlighter shared/prql/code/ regex '\\b\\d+[eE][+-]?\\d+\\b' 0:value\nadd-highlighter shared/prql/code/ regex '(\\b\\d+)?\\.\\d+\\b' 0:value\nadd-highlighter shared/prql/code/ regex '\\b\\d+\\.' 0:value\n\nevaluate-commands %sh{\n    # Grammar\n    values=\"true false null this that\"\n    meta=\"prql module\"\n\n    keywords=\"case let type alias in loop\"\n\n    types=\"bool float int int8 int16 int32 int64 int128 text date time timestamp\"\n\n    functions=\"aggregate derive filter from group join select sort take window\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list prql_static_words $(join \"${values} ${meta} ${keywords} ${types} ${functions}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/prql/code/ regex '\\b($(join \"${values}\" '|'))\\b' 0:value\n        add-highlighter shared/prql/code/ regex '\\b($(join \"${meta}\" '|'))\\b' 0:meta\n        add-highlighter shared/prql/code/ regex '\\b($(join \"${keywords}\" '|'))\\b' 0:keyword\n        add-highlighter shared/prql/code/ regex '\\b($(join \"${functions}\" '|'))\\b\\(' 1:builtin\n        add-highlighter shared/prql/code/ regex '\\b($(join \"${types}\" '|'))\\b' 0:type\n        add-highlighter shared/prql/code/ regex '^\\h*(@\\{[\\w_.]+\\}))' 1:attribute\n    \"\n}\n\nadd-highlighter shared/prql/code/ regex (?<=[\\w\\s\\d\\)\\]'\"_])(<=|>=|<>?|>|!=|==|~=|\\||\\^|&|\\+|-|\\*\\*?|//?|%|~) 0:operator\nadd-highlighter shared/prql/code/ regex (?<=[\\w\\s\\d'\"_])((?<![=<>!]):?=(?![=])|[+*-]=) 0:builtin\nadd-highlighter shared/prql/code/ regex ^\\h*(?:module)\\h+(\\S+) 1:module\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden prql-insert-on-new-line %{ evaluate-commands -itersel -draft %{\n    execute-keys <semicolon>\n    try %{\n        evaluate-commands -draft -save-regs '/\"' %{\n            # Ensure previous line is a comment\n            execute-keys -draft kxs^\\h*#+\\h*<ret>\n\n            # now handle the comment continuation logic\n            try %{\n                # try and match a regular block comment, copying the prefix\n                execute-keys -draft -save-regs '' k x 1s^(\\h*#+\\h*)\\S.*$ <ret> y\n                execute-keys -draft P\n            } catch %{\n                try %{\n                    # try and match a regular block comment followed by a single\n                    # empty comment line\n                    execute-keys -draft -save-regs '' kKx 1s^(\\h*#+\\h*)\\S+\\n\\h*#+\\h*$ <ret> y\n                    execute-keys -draft P\n                } catch %{\n                    try %{\n                        # try and match a pair of empty comment lines, and delete\n                        # them if we match\n                        execute-keys -draft kKx <a-k> ^\\h*#+\\h*\\n\\h*#+\\h*$ <ret> <a-d>\n                    } catch %{\n                        # finally, we need a special case for a new line inserted\n                        # into a file that consists of a single empty comment - in\n                        # that case we can't expect to copy the trailing whitespace,\n                        # so we add our own\n                        execute-keys -draft -save-regs '' k x1s^(\\h*#+)\\h*$<ret> y\n                        execute-keys -draft P\n                        execute-keys -draft i<space>\n                    }\n                }\n            }\n        }\n\n        # trim trailing whitespace on the previous line\n        try %{ execute-keys -draft k x s\\h+$<ret> d }\n    }\n} }\n\ndefine-command -hidden prql-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after line ending with :\n        try %{ execute-keys -draft , k x <a-k> :$ <ret> <a-K> ^\\h*# <ret> j <a-gt> }\n        # deindent closing brace/bracket when after cursor (for arrays and dictionaries)\n        try %< execute-keys -draft x <a-k> ^\\h*[}\\]] <ret> gh / [}\\]] <ret> m <a-S> 1<a-&> >\n    >\n>\n\n§\n"
  },
  {
    "path": "rc/filetype/pug.kak",
    "content": "# Note: jade is changing its name to pug (https://github.com/pugjs/pug/issues/2184)\n# This appears to be a work in progress -- the pug-lang domain is parked, while\n# the jade-lang one is active. This highlighter will recognize .pug and .jade extensions,\n\n# http://jade-lang.com (will be http://pug-lang.com)\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](pug|jade) %{\n    set-option buffer filetype pug\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=pug %{\n    require-module pug\n\n    hook window ModeChange pop:insert:.* -group pug-trim-indent pug-trim-indent\n    hook window InsertChar \\n -group pug-indent pug-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window pug-.+ }\n}\n\nhook -group pug-highlight global WinSetOption filetype=pug %{\n    add-highlighter window/pug ref pug\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/pug }\n}\n\n\nprovide-module pug %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/pug regions\nadd-highlighter shared/pug/code          default-region group\nadd-highlighter shared/pug/text          region ^\\h*\\|\\s     $                      regex   \\h*(\\|) 1:meta\nadd-highlighter shared/pug/text2         region '^\\h*([A-Za-z][A-Za-z0-9_-]*)?(#[A-Za-z][A-Za-z0-9_-]*)?((?:\\.[A-Za-z][A-Za-z0-9_-]*)*)?(?<!\\t)(?<! )(?<!\\n)\\h+\\K.*' $ regex   \\h*(\\|) 1:meta\nadd-highlighter shared/pug/javascript    region ^\\h*[-=!]    $                      ref javascript\nadd-highlighter shared/pug/double_string region '\"'          (?:(?<!\\\\)(\\\\\\\\)*\"|$)  fill string\nadd-highlighter shared/pug/single_string region \"'\"          (?:(?<!\\\\)(\\\\\\\\)*'|$)  fill string\nadd-highlighter shared/pug/comment       region //           $                      fill comment\nadd-highlighter shared/pug/attribute     region -recurse \\( \\(            \\)        group\nadd-highlighter shared/pug/puglang       region ^\\h*\\b(\\block|extends|include|append|prepend|if|unless|else|case|when|default|each|while|mixin)\\b $ group\n\n# Filters\n# ‾‾‾‾‾‾‾\n\nadd-highlighter shared/pug/attribute/       ref     javascript\nadd-highlighter shared/pug/attribute/       regex   [()=]                             0:operator\nadd-highlighter shared/pug/puglang/         ref     javascript\nadd-highlighter shared/pug/puglang/         regex   \\b(\\block|extends|include|append|prepend|if|unless|else|case|when|default|each|while|mixin|of|in)\\b 0:keyword\nadd-highlighter shared/pug/code/            regex   ^\\h*([A-Za-z][A-Za-z0-9_-]*)      1:type\nadd-highlighter shared/pug/code/            regex   '(#[A-Za-z][A-Za-z0-9_-]*)'       1:variable\nadd-highlighter shared/pug/code/            regex   ((?:\\.[A-Za-z][A-Za-z0-9_-]*)*)   1:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden pug-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden pug-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : pug-trim-indent <ret> }\n        # copy '//', '|', '-' or '(!)=' prefix and following whitespace\n        try %{ execute-keys -draft k x s ^\\h*\\K[/|!=-]{1,2}\\h* <ret> y gh j P }\n        # indent unless we copied something above\n        try %{ execute-keys -draft <a-gt> , b s \\S <ret> g l <a-lt> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/purescript.kak",
    "content": "# http://purescript.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n# Syntax reference\n# https://github.com/purescript/documentation/blob/master/language/Syntax.md\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](purs) %{\n    set-option buffer filetype purescript\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=purescript %{\n    require-module purescript\n\n    set-option buffer extra_word_chars '_' \"'\"\n    hook window ModeChange pop:insert:.* -group purescript-trim-indent purescript-trim-indent\n    hook window InsertChar \\n -group purescript-insert purescript-insert-on-new-line\n    hook window InsertChar \\n -group purescript-indent purescript-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window purescript-.+ }\n}\n\nhook -group purescript-highlight global WinSetOption filetype=purescript %{\n    add-highlighter window/purescript ref purescript\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/purescript }\n}\n\n\nprovide-module purescript %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/purescript regions\nadd-highlighter shared/purescript/code default-region group\nadd-highlighter shared/purescript/string       region (?<!'\\\\)(?<!')\"                 (?<!\\\\)(\\\\\\\\)*\"  fill string\nadd-highlighter shared/purescript/comment      region -recurse \\{- \\{-                  -\\}            fill comment\nadd-highlighter shared/purescript/line_comment region --(?:[^!#$%&*+./<>?@\\\\\\^|~=]|$) $                fill comment\n\nadd-highlighter shared/purescript/code/ regex (?<!')\\b0x+[A-Fa-f0-9]+ 0:value\nadd-highlighter shared/purescript/code/ regex (?<!')\\b\\d+([.]\\d+)? 0:value\nadd-highlighter shared/purescript/code/ regex (?<!')\\b(import|hiding|module)(?!')\\b 0:keyword\nadd-highlighter shared/purescript/code/ regex (?<!')\\b(import)(?!')\\b[^\\n]+(?<!')\\b(as)(?!')\\b 2:keyword\nadd-highlighter shared/purescript/code/ regex (?<!')\\b(class|data|default|derive|infix|infixl|infixr|instance|module|newtype|pattern|type|where)(?!')\\b 0:keyword\nadd-highlighter shared/purescript/code/ regex (?<!')\\b(case|do|else|if|in|let|mdo|of|proc|rec|then)(?!')\\b 0:attribute\nadd-highlighter shared/purescript/code/ regex (?<!')\\b(type|data)\\b\\s+(\\bfamily\\b)?(?!') 0:keyword\n\n# The complications below is because period has many uses:\n# As function composition operator (possibly without spaces) like \".\" and \"f.g\"\n# Hierarchical modules like \"Data.Maybe\"\n# Qualified imports like \"Data.Maybe.Just\", \"Data.Maybe.maybe\", \"Control.Applicative.<$>\"\n# Quantifier separator in \"forall a . [a] -> [a]\"\n# Enum comprehensions like \"[1..]\" and \"[a..b]\" (making \"..\" and \"Module...\" illegal)\n\n# matches uppercase identifiers:  Monad Control.Monad\n# not non-space separated dot:    Just.const\nadd-highlighter shared/purescript/code/ regex \\b([A-Z]['\\w]*\\.)*[A-Z]['\\w]*(?!['\\w])(?![.a-z]) 0:variable\n\n# matches infix identifier: `mod` `Apa._T'M`\nadd-highlighter shared/purescript/code/ regex `\\b([A-Z]['\\w]*\\.)*[\\w]['\\w]*` 0:operator\n# matches imported operators: M.! M.. Control.Monad.>>\n# not operator keywords:      M... M.->\nadd-highlighter shared/purescript/code/ regex \\b[A-Z]['\\w]*\\.[~<=>|:!?/.@$*&#%+\\^\\-\\\\]+ 0:operator\n# matches dot: .\n# not possibly incomplete import:  a.\n# not other operators:             !. .!\nadd-highlighter shared/purescript/code/ regex (?<![\\w~<=>|:!?/.@$*&#%+\\^\\-\\\\])\\.(?![~<=>|:!?/.@$*&#%+\\^\\-\\\\]) 0:operator\n# matches other operators: ... > < <= ^ <*> <$> etc\n# not dot: .\n# not operator keywords:  @ .. -> :: ~\nadd-highlighter shared/purescript/code/ regex (?<![~<=>|:!?/.@$*&#%+\\^\\-\\\\])[~<=>|:!?/.@$*&#%+\\^\\-\\\\]+ 0:operator\n\n# matches operator keywords: @ ->\nadd-highlighter shared/purescript/code/ regex (?<![~<=>|:!?/.@$*&#%+\\^\\-\\\\])(@|~|<-|->|=>|::|=|:|[|])(?![~<=>|:!?/.@$*&#%+\\^\\-\\\\]) 1:keyword\n# matches: forall [..variables..] .\n# not the variables\nadd-highlighter shared/purescript/code/ regex \\b(forall|∀)\\b[^.\\n]*?(\\.) 1:keyword 2:keyword\n\n# matches 'x' '\\\\' '\\'' '\\n' '\\0'\n# not incomplete literals: '\\'\n# not valid identifiers:   w' _'\nadd-highlighter shared/purescript/code/ regex \\B'([^\\\\]|[\\\\]['\"\\w\\d\\\\])' 0:string\n# this has to come after operators so '-' etc is correct\n\n# matches function names in type signatures\nadd-highlighter shared/purescript/code/ regex ^\\s*(?:where\\s+|let\\s+|default\\s+)?([_a-z]['\\w]*#?(?:,\\s*[_a-z]['\\w]*#?)*)\\s+::\\s 1:meta\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden purescript-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden purescript-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy -- comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K--\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden purescript-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # align to first clause\n        try %{ execute-keys -draft <semicolon> k x X s ^\\h*(if|then|else)?\\h*(([\\w']+\\h+)+=)?\\h*(case\\h+[\\w']+\\h+of|do|let|where)\\h+\\K.* <ret> s \\A|.\\z <ret> & }\n        # filter previous line\n        try %{ execute-keys -draft k : purescript-trim-indent <ret> }\n        # indent after lines beginning with condition or ending with expression or =(\n        try %{ execute-keys -draft <semicolon> k x <a-k> ^\\h*if|[=(]$|\\b(case\\h+[\\w']+\\h+of|do|let|where)$ <ret> j <a-gt> }\n    }\n}\n\n§\n"
  },
  {
    "path": "rc/filetype/python.kak",
    "content": "# http://python.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](py) %{\n    set-option buffer filetype python\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=python %{\n    require-module python\n\n    set-option window static_words %opt{python_static_words}\n\n    hook window InsertChar \\n -group python-insert python-insert-on-new-line\n    hook window InsertChar \\n -group python-indent python-indent-on-new-line\n    # cleanup trailing whitespaces on current line insert end\n    hook window ModeChange pop:insert:.* -group python-trim-indent %{ try %{ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d } }\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window python-.+ }\n}\n\nhook -group python-highlight global WinSetOption filetype=python %{\n    add-highlighter window/python ref python\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/python }\n}\n\nprovide-module python %§\n\n# Highlighters & Completion\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/python regions\nadd-highlighter shared/python/code default-region group\nadd-highlighter shared/python/docstring     region -match-capture ^\\h*(\"\"\"|''') (?<!\\\\)(?:\\\\\\\\)*(\"\"\"|''') regions\n\nadd-highlighter shared/python/documentation region '##'  '$'              fill documentation\nadd-highlighter shared/python/comment       region '#'   '$'              fill comment\n\n# String interpolation\nadd-highlighter shared/python/f_triple_string region -match-capture [fF](\"\"\"|''') (?<!\\\\)(?:\\\\\\\\)*(\"\"\"|''') group\nadd-highlighter shared/python/f_triple_string/ fill string\nadd-highlighter shared/python/f_triple_string/ regex \\{.*?\\} 0:value\n\nadd-highlighter shared/python/f_double_string region '[fF]\"'   (?<!\\\\)(\\\\\\\\)*\" group\nadd-highlighter shared/python/f_double_string/ fill string\nadd-highlighter shared/python/f_double_string/ regex \\{.*?\\} 0:value\n\nadd-highlighter shared/python/f_single_string region \"[fF]'\"   (?<!\\\\)(\\\\\\\\)*' group\nadd-highlighter shared/python/f_single_string/ fill string\nadd-highlighter shared/python/f_single_string/ regex \\{.*?\\} 0:value\n\n\n# Regular string\nadd-highlighter shared/python/triple_string region -match-capture (\"\"\"|''') (?<!\\\\)(?:\\\\\\\\)*(\"\"\"|''') fill string\nadd-highlighter shared/python/double_string region '\"'   (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/python/single_string region \"'\"   (?<!\\\\)(\\\\\\\\)*' fill string\n\n# Integer formats\nadd-highlighter shared/python/code/ regex '(?i)\\b0b[01]+l?\\b' 0:value\nadd-highlighter shared/python/code/ regex '(?i)\\b0x[\\da-f]+l?\\b' 0:value\nadd-highlighter shared/python/code/ regex '(?i)\\b0o?[0-7]+l?\\b' 0:value\nadd-highlighter shared/python/code/ regex '(?i)\\b([1-9]\\d*|0)l?\\b' 0:value\n# Float formats\nadd-highlighter shared/python/code/ regex '\\b\\d+[eE][+-]?\\d+\\b' 0:value\nadd-highlighter shared/python/code/ regex '(\\b\\d+)?\\.\\d+\\b' 0:value\nadd-highlighter shared/python/code/ regex '\\b\\d+\\.' 0:value\n# Imaginary formats\nadd-highlighter shared/python/code/ regex '\\b\\d+\\+\\d+[jJ]\\b' 0:value\n\nadd-highlighter shared/python/docstring/ default-region fill documentation\nadd-highlighter shared/python/docstring/ region '(>>>|\\.\\.\\.) \\K'    (?=''')|(?=\"\"\") ref python\n\nevaluate-commands %sh{\n    # Grammar\n    values=\"True False None self inf\"\n    meta=\"import from\"\n\n    # attributes and methods list based on https://docs.python.org/3/reference/datamodel.html\n    attributes=\"__annotations__ __closure__ __code__ __defaults__ __dict__ __doc__\n                __globals__ __kwdefaults__ __module__ __name__ __qualname__\"\n    methods=\"__abs__ __add__ __aenter__ __aexit__ __aiter__ __and__ __anext__\n             __await__ __bool__ __bytes__ __call__ __complex__ __contains__\n             __del__ __delattr__ __delete__ __delitem__ __dir__ __divmod__\n             __enter__ __eq__ __exit__ __float__ __floordiv__ __format__\n             __ge__ __get__ __getattr__ __getattribute__ __getitem__\n             __gt__ __hash__ __iadd__ __iand__ __ifloordiv__ __ilshift__\n             __imatmul__ __imod__ __imul__ __index__ __init__\n             __init_subclass__ __int__ __invert__ __ior__ __ipow__\n             __irshift__ __isub__ __iter__ __itruediv__ __ixor__ __le__\n             __len__ __length_hint__ __lshift__ __lt__ __matmul__\n             __missing__ __mod__ __mul__ __ne__ __neg__ __new__ __or__\n             __pos__ __pow__ __radd__ __rand__ __rdivmod__ __repr__\n             __reversed__ __rfloordiv__ __rlshift__ __rmatmul__ __rmod__\n             __rmul__ __ror__ __round__ __rpow__ __rrshift__ __rshift__\n             __rsub__ __rtruediv__ __rxor__ __set__ __setattr__\n             __setitem__ __set_name__ __slots__ __str__ __sub__\n             __truediv__ __xor__\"\n\n    # built-in exceptions https://docs.python.org/3/library/exceptions.html\n    exceptions=\"ArithmeticError AssertionError AttributeError BaseException BlockingIOError\n                BrokenPipeError BufferError BytesWarning ChildProcessError\n                ConnectionAbortedError ConnectionError ConnectionRefusedError\n                ConnectionResetError DeprecationWarning EOFError Exception\n                FileExistsError FileNotFoundError FloatingPointError FutureWarning\n                GeneratorExit ImportError ImportWarning IndentationError\n                IndexError InterruptedError IsADirectoryError KeyboardInterrupt\n                KeyError LookupError MemoryError ModuleNotFoundError NameError\n                NotADirectoryError NotImplementedError OSError OverflowError\n                PendingDeprecationWarning PermissionError ProcessLookupError\n                RecursionError ReferenceError ResourceWarning RuntimeError\n                RuntimeWarning StopAsyncIteration StopIteration SyntaxError\n                SyntaxWarning SystemError SystemExit TabError TimeoutError TypeError\n                UnboundLocalError UnicodeDecodeError UnicodeEncodeError UnicodeError\n                UnicodeTranslateError UnicodeWarning UserWarning ValueError Warning\n                ZeroDivisionError\"\n\n    # Keyword list is collected using `keyword.kwlist` from `keyword`\n    keywords=\"and as assert async await break class continue def del elif else except exec\n              finally for global if in is lambda nonlocal not or pass print\n              raise return try while with yield\"\n\n    # Collected from `keyword.softkwlist`\n    soft_keywords=\"_ case match\"\n\n    types=\"bool buffer bytearray bytes complex dict file float frozenset int\n           list long memoryview object set str tuple unicode xrange\"\n\n    functions=\"abs all any ascii bin breakpoint callable chr classmethod compile complex\n               delattr dict dir divmod enumerate eval exec filter\n               format frozenset getattr globals hasattr hash help\n               hex id __import__ input isinstance issubclass iter\n               len locals map max memoryview min next oct open ord\n               pow print property range repr reversed round\n               setattr slice sorted staticmethod sum super type vars zip\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list python_static_words $(join \"${values} ${meta} ${attributes} ${methods} ${exceptions} ${keywords} ${types} ${functions}\" ' ')\"\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/python/code/ regex '\\b($(join \"${values}\" '|'))\\b' 0:value\n        add-highlighter shared/python/code/ regex '\\b($(join \"${meta}\" '|'))\\b' 0:meta\n        add-highlighter shared/python/code/ regex '\\b($(join \"${attributes}\" '|'))\\b' 0:attribute\n        add-highlighter shared/python/code/ regex '\\bdef\\s+($(join \"${methods}\" '|'))\\b' 1:function\n        add-highlighter shared/python/code/ regex '\\b($(join \"${exceptions}\" '|'))\\b' 0:function\n        add-highlighter shared/python/code/ regex '\\b($(join \"${keywords} ${soft_keywords}\" '|'))\\b' 0:keyword\n        add-highlighter shared/python/code/ regex '\\b($(join \"${functions}\" '|'))\\b\\(' 1:builtin\n        add-highlighter shared/python/code/ regex '\\b($(join \"${types}\" '|'))\\b' 0:type\n        add-highlighter shared/python/code/ regex '^\\h*(@[\\w_.]+))' 1:attribute\n    \"\n}\n\nadd-highlighter shared/python/code/ regex (?<=[\\w\\s\\d\\)\\]'\"_])(<=|<<|>>|>=|<>?|>|!=|==|\\||\\^|&|\\+|-|\\*\\*?|//?|%|~) 0:operator\nadd-highlighter shared/python/code/ regex (?<=[\\w\\s\\d'\"_])((?<![=<>!]):?=(?![=])|[+*-]=) 0:builtin\nadd-highlighter shared/python/code/ regex ^\\h*(?:from|import)\\h+(\\S+) 1:module\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden python-insert-on-new-line %{ evaluate-commands -itersel -draft %{\n    execute-keys <semicolon>\n    try %{\n        evaluate-commands -draft -save-regs '/\"' %{\n            # Ensure previous line is a comment\n            execute-keys -draft kxs^\\h*#+\\h*<ret>\n\n            # now handle the coment continuation logic\n            try %{\n                # try and match a regular block comment, copying the prefix\n                execute-keys -draft -save-regs '' k x 1s^(\\h*#+\\h*)\\S.*$ <ret> y\n                execute-keys -draft P\n            } catch %{\n                try %{\n                    # try and match a regular block comment followed by a single\n                    # empty comment line\n                    execute-keys -draft -save-regs '' kKx 1s^(\\h*#+\\h*)\\S+\\n\\h*#+\\h*$ <ret> y\n                    execute-keys -draft P\n                } catch %{\n                    try %{\n                        # try and match a pair of empty comment lines, and delete\n                        # them if we match\n                        execute-keys -draft kKx <a-k> ^\\h*#+\\h*\\n\\h*#+\\h*$ <ret> <a-d>\n                    } catch %{\n                        # finally, we need a special case for a new line inserted\n                        # into a file that consists of a single empty comment - in\n                        # that case we can't expect to copy the trailing whitespace,\n                        # so we add our own\n                        execute-keys -draft -save-regs '' k x1s^(\\h*#+)\\h*$<ret> y\n                        execute-keys -draft P\n                        execute-keys -draft i<space>\n                    }\n                }\n            }\n        }\n\n        # trim trailing whitespace on the previous line\n        try %{ execute-keys -draft k x s\\h+$<ret> d }\n    }\n} }\n\ndefine-command -hidden python-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n        # indent after line ending with :\n        try %{ execute-keys -draft , k x <a-k> :$ <ret> <a-K> ^\\h*# <ret> j <a-gt> }\n        # deindent closing brace/bracket when after cursor (for arrays and dictionaries)\n        try %< execute-keys -draft x <a-k> ^\\h*[}\\]] <ret> gh / [}\\]] <ret> m <a-S> 1<a-&> >\n    >\n>\n\n§\n"
  },
  {
    "path": "rc/filetype/r.kak",
    "content": "# http://kakoune.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate (.*/)?(\\.Rprofile|.*\\.[rR]) %{\n    set-option buffer filetype r\n}\n\nprovide-module r %§\n\n# Highlighters & Completion\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/r regions\nadd-highlighter shared/r/code default-region group\nadd-highlighter shared/r/double_string region '\"'   (?<!\\\\)(\\\\\\\\)*\"  fill string\nadd-highlighter shared/r/single_string region \"'\"   (?<!\\\\)(\\\\\\\\)*'  fill string\nadd-highlighter shared/r/comment       region '#'   '$'              fill comment\nadd-highlighter shared/r/identifier    region '`'   (?<!\\\\)(\\\\\\\\)*`  fill attribute\n\n# see base::NumericConstants\nadd-highlighter shared/r/code/ regex '(?i)\\b(?<![.\\d])\\d+(\\.\\d*)?(e[-+]?\\d+)?(?I)[iL]?(?![\\d.e\\w])' 0:value\nadd-highlighter shared/r/code/ regex '(?i)(?<![.\\d\\w])\\.\\d+(e[-+]?\\d+)?(?I)[iL]?(?![\\d.e])' 0:value\nadd-highlighter shared/r/code/ regex '(?i)\\b(?<![.\\d])0x[0-9a-f]+?(p[-+]?\\d+)?(?I)[iL]?(?![\\d.e\\w])' 0:value\n\nevaluate-commands %sh{\n    # see base::Reserved\n    values=\"TRUE|FALSE|NULL|Inf|NaN|NA|NA_integer_|NA_real_|NA_complex_|NA_character_|\\.{3}|\\.{2}\\d+|\"\n    keywords=\"if|else|repeat|while|function|for|in|next|break\"\n\n    # see base::Ops and methods::Ops\n    math_functions=\"abs|sign|sqrt|floor|ceiling|trunc|round|signif|exp|log|expm1|log1p|cos|sin|tan|cospi|sinpi|tanpi|acos|asin|atan|cosh|sinh|tanh|acosh|asinh|atanh|lgamma|gamma|digamma|trigamma\"\n    summary_functions=\"all|any|sum|prod|min|max|range\"\n    complex_functions=\"Arg|Conj|Im|Mod|Re\"\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"hook global WinSetOption filetype=python %{\n        set-option window static_words ${values} ${keywords} ${math_functions} ${summary_functions} ${complex_functions}\n    }\" | tr '|' ' '\n\n    printf %s \"\n        add-highlighter shared/r/code/ regex '\\b(${values})\\b' 0:value\n        add-highlighter shared/r/code/ regex '\\b(${keywords})\\b' 0:keyword\n        add-highlighter shared/r/code/ regex '\\b(${math_functions}|${summary_functions}|${complex_functions})\\b' 0:function\n    \"\n}\n\n# see base::Syntax\nadd-highlighter shared/r/code/ regex (?<=[\\w\\s\\d'\"_)])(\\$|@|\\^|-|\\+|%[^%^\\n]+%|\\*|/|<|>|<=|>=|!|&{1,2}|\\|{1,2}|~|\\?|:{1,3}|\\[{1,2}|\\]{1,2}|={1,2}|<{1,2}-|->{1,2}|!=|%%) 0:operator\n\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden r-trim-indent %{\n    # remove the line if it's empty when leaving the insert mode\n    try %{ execute-keys -draft x 1s^(\\h+)$<ret> d }\n}\n\ndefine-command -hidden r-indent-on-newline %< evaluate-commands -draft -itersel %<\n    execute-keys <semicolon>\n    try %<\n        # if previous line closed a paren (possibly followed by words and a comment),\n        # copy indent of the opening paren line\n        execute-keys -draft kx 1s(\\))(\\h+\\w+)*\\h*(\\;\\h*)?(?:#[^\\n]+)?\\n\\z<ret> m<a-semicolon>J <a-S> 1<a-&>\n    > catch %<\n        # else indent new lines with the same level as the previous one\n        execute-keys -draft K <a-&>\n    >\n    # remove previous empty lines resulting from the automatic indent\n    try %< execute-keys -draft k x <a-k>^\\h+$<ret> Hd >\n    # indent after an opening brace or parenthesis at end of line\n    try %< execute-keys -draft k x s[{(]\\h*$<ret> j <a-gt> >\n    # indent after a statement not followed by an opening brace\n    try %< execute-keys -draft k x s\\)\\h*(?:#[^\\n]+)?\\n\\z<ret> \\\n                               <a-semicolon>mB <a-k>\\A\\b(if|for|while)\\b<ret> <a-semicolon>j <a-gt> >\n    try %< execute-keys -draft k x s \\belse\\b\\h*(?:#[^\\n]+)?\\n\\z<ret> \\\n                               j <a-gt> >\n    # deindent after a single line statement end\n    try %< execute-keys -draft K x <a-k>\\;\\h*(#[^\\n]+)?$<ret> \\\n                               K x s\\)(\\h+\\w+)*\\h*(#[^\\n]+)?\\n([^\\n]*\\n){2}\\z<ret> \\\n                               MB <a-k>\\A\\b(if|for|while)\\b<ret> <a-S>1<a-&> >\n    try %< execute-keys -draft K x <a-k>\\;\\h*(#[^\\n]+)?$<ret> \\\n                               K x s \\belse\\b\\h*(?:#[^\\n]+)?\\n([^\\n]*\\n){2}\\z<ret> \\\n                               <a-S>1<a-&> >\n    # align to the opening parenthesis or opening brace (whichever is first)\n    # on a previous line if its followed by text on the same line\n    try %< evaluate-commands -draft %<\n        # Go to opening parenthesis and opening brace, then select the most nested one\n        try %< execute-keys [c [({],[)}] <ret> >\n        # Validate selection and get first and last char\n        execute-keys <a-k>\\A[{(](\\h*\\S+)+\\n<ret> <a-K>\"(([^\"]*\"){2})*<ret> <a-K>'(([^']*'){2})*<ret> <a-:><a-semicolon>L <a-S>\n        # Remove possibly incorrect indent from new line which was copied from previous line\n        try %< execute-keys -draft , <a-h> s\\h+<ret> d >\n        # Now indent and align that new line with the opening parenthesis/brace\n        execute-keys 1<a-&> &\n     > >\n> >\n\ndefine-command -hidden r-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> <a-S> 1<a-&> ]\n]\n\ndefine-command -hidden r-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[\n        # in case open curly brace follows a closing paren, align indent with opening paren\n        execute-keys -itersel -draft <a-h><a-:><a-k>^\\h+\\}$<ret>hm <a-F>)M <a-k> \\A\\(.*\\)\\h\\{.*\\}\\z <ret> <a-S>1<a-&>\n    ] catch %[\n        # otherwise align with open curly brace\n        execute-keys -itersel -draft <a-h><a-:><a-k>^\\h+\\}$<ret>hm<a-S>1<a-&>\n    ] catch %[]\n]\n\ndefine-command -hidden r-insert-on-newline %[ evaluate-commands -itersel -draft %[\n    execute-keys <semicolon>\n    try %[\n        evaluate-commands -draft -save-regs '/\"' %[\n            # copy the commenting prefix\n            execute-keys -save-regs '' k x1s^\\h*(#+\\h*)<ret> y\n            try %[\n                # if the previous comment isn't empty, create a new one\n                execute-keys x<a-K>^\\h*#+\\h*$<ret> jxs^\\h*<ret>P\n            ] catch %[\n                # if there is no text in the previous comment, remove it completely\n                execute-keys d\n            ]\n        ]\n    ]\n] ]\n\n§\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook -group r-highlight global WinSetOption filetype=r %{\n    require-module r\n    add-highlighter window/r ref r\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/r }\n}\n\nhook global WinSetOption filetype=r %~\n    require-module r\n    hook window ModeChange pop:insert:.* r-trim-indent\n    hook window InsertChar \\n        r-insert-on-newline\n    hook window InsertChar \\n        r-indent-on-newline\n    hook window InsertChar \\{        r-indent-on-opening-curly-brace\n    hook window InsertChar \\}        r-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window r-.+ }\n~\n"
  },
  {
    "path": "rc/filetype/ragel.kak",
    "content": "# http://complang.org/ragel\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# ragel.kak does not try to detect host language.\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](ragel|rl) %{\n    set-option buffer filetype ragel\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=ragel %{\n    require-module ragel\n\n    hook window ModeChange pop:insert:.* -group ragel-trim-indent ragel-trim-indent\n    hook window InsertChar .* -group ragel-indent ragel-indent-on-char\n    hook window InsertChar \\n -group ragel-insert ragel-insert-on-new-line\n    hook window InsertChar \\n -group ragel-indent ragel-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window ragel-.+ }\n}\n\nhook -group ragel-highlight global WinSetOption filetype=ragel %{\n    add-highlighter window/ragel ref ragel\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/ragel }\n}\n\nprovide-module ragel %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/ragel regions\nadd-highlighter shared/ragel/code default-region group\nadd-highlighter shared/ragel/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\"         fill string\nadd-highlighter shared/ragel/single_string region \"'\" \"'\"                     fill string\nadd-highlighter shared/ragel/comment region '#' '$'                     fill comment\n\nadd-highlighter shared/ragel/code/ regex \\b(true|false)\\b 0:value\nadd-highlighter shared/ragel/code/ regex '%%\\{|\\}%%|<\\w+>' 0:variable\nadd-highlighter shared/ragel/code/ regex :=|=>|->|:>|:>>|<: 0:operator\nadd-highlighter shared/ragel/code/ regex \\b(action|alnum|alpha|any|ascii|case|cntrl|contained|context|data|digit|empty|eof|err|error|exec|export|exports|extend|fblen|fbreak|fbuf|fc|fcall|fcurs|fentry|fexec|fgoto|fhold|first_final|fnext|fpc|fret|from|fstack|ftargs|graph|import|include|init|inwhen|lerr|lower|machine|nocs|noend|noerror|nofinal|noprefix|outwhen|postpop|prepush|print|punct|range|space|start|to|upper|when|write|xdigit|zlen)\\b 0:keyword\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden ragel-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden ragel-indent-on-char %<\n    evaluate-commands -draft -itersel %<\n        # align closer token to its opener when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h+[\\]})]$ <ret>        m         s \\A|.\\z <ret> 1<a-&> >\n        try %< execute-keys -draft <a-h> <a-k> ^\\h+   [*]$ <ret> <a-?> [*]$ <ret> s \\A|.\\z <ret> 1<a-&> >\n    >\n>\n\ndefine-command -hidden ragel-insert-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # copy _#_ comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n    >\n>\n\ndefine-command -hidden ragel-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : ragel-trim-indent <ret> }\n        # indent after lines ending with opener token\n        try %< execute-keys -draft k x <a-k> [[{(*]$ <ret> j <a-gt> >\n        # align closer token to its opener when after cursor\n        try %< execute-keys -draft x <a-k> ^\\h*[})\\]] <ret> gh / [})\\]] <ret> m <a-S> 1<a-&> >\n    >\n>\n\n§\n"
  },
  {
    "path": "rc/filetype/restructuredtext.kak",
    "content": "# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](rst) %{\n    set-option buffer filetype restructuredtext\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=restructuredtext %{\n    require-module restructuredtext\n}\n\nhook -group restructuredtext-load-languages global WinSetOption filetype=restructuredtext %{\n    restructuredtext-load-languages '%'\n}\n\nhook -group restructuredtext-load-languages global WinSetOption filetype=restructuredtext %{\n    hook -group restructuredtext-load-languages window NormalIdle .* %{restructuredtext-load-languages gtGbGl}\n    hook -group restructuredtext-load-languages window InsertIdle .* %{restructuredtext-load-languages gtGbGl}\n}\n\nhook -group restructuredtext-highlight global WinSetOption filetype=restructuredtext %{\n    add-highlighter window/restructuredtext ref restructuredtext\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/restructuredtext }\n}\n\nprovide-module restructuredtext %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/restructuredtext regions\nadd-highlighter shared/restructuredtext/content default-region group\nadd-highlighter shared/restructuredtext/code region ::\\h*\\n ^(?=\\S)  fill meta\n\n# Setext-style header\n# Valid header characters:\n# # ! \" $ % & ' ( ) * + , - . / : ; < = > ? @ [ \\ ] ^ _ ` { | } ~\n\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(#{3,}\\n)?[^\\n]+\\n(#{3,})$ 0:title\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(!{3,}\\n)?[^\\n]+\\n(!{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\"{3,}\\n)?[^\\n]+\\n(\"{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\${3,}\\n)?[^\\n]+\\n(\\${3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(%{3,}\\n)?[^\\n]+\\n(%{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(&{3,}\\n)?[^\\n]+\\n(&{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)('{3,}\\n)?[^\\n]+\\n('{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\({3,}\\n)?[^\\n]+\\n(\\({3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\){3,}\\n)?[^\\n]+\\n(\\){3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\*{3,}\\n)?[^\\n]+\\n(\\*{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\+{3,}\\n)?[^\\n]+\\n(\\+{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(,{3,}\\n)?[^\\n]+\\n(,{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(-{3,}\\n)?[^\\n]+\\n(-{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\.{3,}\\n)?[^\\n]+\\n(\\.{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(/{3,}\\n)?[^\\n]+\\n(/{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(:{3,}\\n)?[^\\n]+\\n(:{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\;{3,}\\n)?[^\\n]+\\n(\\;{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(<{3,}\\n)?[^\\n]+\\n(<{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(={3,}\\n)?[^\\n]+\\n(={3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(>{3,}\\n)?[^\\n]+\\n(>{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\?{3,}\\n)?[^\\n]+\\n(\\?{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(@{3,}\\n)?[^\\n]+\\n(@{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\[{3,}\\n)?[^\\n]+\\n(\\[{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\\\{3,}\\n)?[^\\n]+\\n(\\\\{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\]{3,}\\n)?[^\\n]+\\n(\\]{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\^{3,}\\n)?[^\\n]+\\n(\\^{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(_{3,}\\n)?[^\\n]+\\n(_{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(`{3,}\\n)?[^\\n]+\\n(`{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\{{3,}\\n)?[^\\n]+\\n(\\{{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\|{3,}\\n)?[^\\n]+\\n(\\|{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(\\}{3,}\\n)?[^\\n]+\\n(\\}{3,})$ 0:header\nadd-highlighter shared/restructuredtext/content/ regex (\\A|\\n\\n)(~{3,}\\n)?[^\\n]+\\n(~{3,})$ 0:header\n\n# Inline markup\nadd-highlighter shared/restructuredtext/content/ regex [^*](\\*\\*([^\\s*]|([^\\s*][^*]*[^\\s*]))\\*\\*)[^*] 1:+b\nadd-highlighter shared/restructuredtext/content/ regex [^*](\\*([^\\s*]|([^\\s*][^*]*[^\\s*]))\\*)[^*] 1:+i\nadd-highlighter shared/restructuredtext/content/ regex [^`](``([^\\s`]|([^\\s`][^`]*[^\\s`]))``)[^`] 1:mono\n\ndefine-command restructuredtext-load-languages -params 1 %{\n    evaluate-commands -draft %{ try %{\n        execute-keys \"%arg{1}s^\\.\\.\\h*code-block::\\h*\\K\\w+<ret>\"\n        evaluate-commands -itersel %{ try %{\n            require-module %val{selection}\n            add-highlighter \"shared/restructuredtext/%val{selection}\" region \"\\.\\.\\h*code-block::\\h*%val{selection}\\h*\\n\" '^(?=\\S)' regions\n            add-highlighter \"shared/restructuredtext/%val{selection}/\" default-region fill meta\n            add-highlighter \"shared/restructuredtext/%val{selection}/inner\" region \\A\\.\\.\\h*code-block::[^\\n]*\\K '^(?=\\S)' ref %val{selection}\n        }}\n    }}\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/ron.kak",
    "content": "# http://doc.rs/ron\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](ron) %{\n    set-option buffer filetype ron\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=ron %{\n    require-module ron\n\n    hook window ModeChange pop:insert:.* -group ron-trim-indent ron-trim-indent\n    hook window InsertChar \\n -group ron-indent ron-indent-on-new-line\n    hook window InsertChar \\{ -group ron-indent ron-indent-on-opening-curly-brace\n    hook window InsertChar [)}\\]] -group ron-indent ron-indent-on-closing\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window ron-.+ }\n}\n\nhook -group ron-highlight global WinSetOption filetype=ron %{\n    add-highlighter window/ron ref ron\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/ron }\n}\n\nprovide-module ron %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/ron regions\nadd-highlighter shared/ron/code default-region group\nadd-highlighter shared/ron/string region %{(?<!')\"} (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/ron/raw_string region -match-capture %{(?<!')r(#*)\"} %{\"(#*)} fill string\n\nadd-highlighter shared/ron/line_comment1 region // $ group\nadd-highlighter shared/ron/line_comment1/comment fill comment\nadd-highlighter shared/ron/line_comment1/todo regex (TODO|NOTE|FIXME): 1:meta\nadd-highlighter shared/ron/line_comment2 region //[!/]{2} $ fill comment\n\nadd-highlighter shared/ron/block_comment1   region -recurse /\\* /\\* \\*/ group\nadd-highlighter shared/ron/block_comment1/comment fill comment\nadd-highlighter shared/ron/block_comment1/todo regex (TODO|NOTE|FIXME): 1:meta\n\nadd-highlighter shared/ron/code/values regex \\b(?:self|true|false|[0-9][_0-9]*(?:\\.[0-9][_0-9]*|(?:\\.[0-9][_0-9]*)?E[\\+\\-][_0-9]+)(?:f(?:32|64))?|(?:0x[_0-9a-fA-F]+|0o[_0-7]+|0b[_01]+|[0-9][_0-9]*)(?:(?:i|u|f)(?:8|16|32|64|128|size))?)\\b 0:value\n\nadd-highlighter shared/ron/code/enum_variant regex \\b(Some|None|Ok|Err)\\b 0:value\n\nadd-highlighter shared/ron/code/ regex ^(\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\nadd-highlighter shared/ron/code/ regex ,(\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\nadd-highlighter shared/ron/code/ regex \\{(\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\nadd-highlighter shared/ron/code/ regex \\((\\s*[^,:\\[\\]\\{\\}\\s]+): 1:variable\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden ron-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden ron-indent-on-new-line %~\n    evaluate-commands -draft -itersel %@\n        try %{\n            try %[ # line comment\n                evaluate-commands -draft -save-regs '/\"' %[\n                    # copy the commenting prefix\n                    execute-keys -save-regs '' k x s ^\\h*//[!/]{0,2}\\h* <ret> y\n                    try %[\n                        # if the previous comment isn't empty, create a new one\n                        execute-keys x<a-K>^\\h*//[!/]{0,2}$<ret> jxs^\\h*<ret>P\n                    ] catch %[\n                        # TODO figure out a way to not delete empty comment in current line\n                        # if there is no space and text in the previous comment, remove it completely\n                        execute-keys s //.*<ret> d\n                    ]\n                ]\n            ] catch %[ # block comment\n                # if the previous line isn't within a comment scope, break\n                execute-keys -draft kx <a-k>^(\\h*/\\*|\\h+\\*(?!/))<ret>\n\n                # find comment opening, validate it was not closed, and check its using star prefixes\n                execute-keys -draft <a-?>/\\*<ret><a-H> <a-K>\\*/<ret> <a-k>\\A\\h*/\\*([^\\n]*\\n\\h*\\*)*[^\\n]*\\n\\h*.\\z<ret>\n\n                try %[\n                    # if the previous line is opening the comment, insert star preceeded by space\n                    execute-keys -draft kx<a-k>^\\h*/\\*<ret>\n                    execute-keys -draft i*<space><esc>\n                ] catch %[\n                    try %[\n                        # if the next line is a comment line insert a star\n                        execute-keys -draft jx<a-k>^\\h+\\*<ret>\n                        execute-keys -draft i*<space><esc>\n                    ] catch %[\n                        try %[\n                            # if the previous line is an empty comment line, close the comment scope\n                            execute-keys -draft kx<a-k>^\\h+\\*\\h+$<ret> x1s\\*(\\h*)<ret>c/<esc>\n                        ] catch %[\n                            # if the previous line is a non-empty comment line, add a star\n                            execute-keys -draft i*<space><esc>\n                        ]\n                    ]\n                ]\n\n                # trim trailing whitespace on the previous line\n                try %[ execute-keys -draft s\\h+$<ret> d ]\n                # align the new star with the previous one\n                execute-keys Kx1s^[^*]*(\\*)<ret>&\n            ]\n        } catch %`\n            # re-indent previous line if it starts with where to match previous block\n            # string literal parsing within extern does not handle escape\n            try %% execute-keys -draft k x <a-k> ^\\h*where\\b <ret> hh <a-?> ^\\h*\\b(impl|((|pub\\ |pub\\((crate|self|super|in\\ (::)?([a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+)(::[a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+)*)\\)\\ )((async\\ |const\\ )?(unsafe\\ )?(extern\\ (\"[^\"]*\"\\ )?)?fn|struct|enum|union)))\\b <ret> <a-S> 1<a-&> %\n            # preserve previous line indent\n            try %{ execute-keys -draft <semicolon> K <a-&> }\n            # indent after lines ending with [{([].+ and move first parameter to own line\n            try %< execute-keys -draft [c[({[],[)}\\]] <ret> <a-k> \\A[({[][^\\n]+\\n[^\\n]*\\n?\\z <ret> L i<ret><esc> <gt> <a-S> <a-&> >\n            # indent after non-empty lines not starting with operator and not ending with , or ; or {\n            # XXX simplify this into a single <a-k> without s\n            try %< execute-keys -draft k x s [^\\h].+ <ret> <a-K> \\A[-+*/&|^})<gt><lt>#] <ret> <a-K> [,<semicolon>{](\\h*/[/*].*|)$ <ret> j <a-gt> >\n            # indent after lines ending with {\n            try %+ execute-keys -draft k x <a-k> \\{$ <ret> j <a-gt> +\n            # dedent after lines starting with . and ending with } or ) or , or ; or .await (} or ) or .await maybe with ?)\n            try %_ execute-keys -draft k x <a-k> ^\\h*\\. <ret> <a-k> ([,<semicolon>]|(([})]|\\.await)\\?*))\\h*$ <ret> j <a-lt> _\n            # dedent after lines ending with \" => {}\" - part of empty match\n            try %# execute-keys -draft k x <a-k> \\ =>\\ \\{\\}\\h*$ <ret> j <a-lt> #\n            # align to opening curly brace or paren when newline is inserted before a single closing\n            try %< execute-keys -draft <a-h> <a-k> ^\\h*[)}] <ret> h m <a-S> 1<a-&> >\n            # todo dedent additional unmatched parenthesis\n            # try %& execute-keys -draft k x s \\((?:[^)(]+|\\((?:[^)(]+|\\([^)(]*\\))*\\))*\\) l Gl s\\) %sh{\n                # count previous selections length\n                # printf \"j $(echo $kak_selections_length | wc -w) <a-lt>\"\n            # } &\n        `\n        # filter previous line\n        try %{ execute-keys -draft k : ron-trim-indent <ret> }\n    @\n~\n\ndefine-command -hidden ron-indent-on-opening-curly-brace %[\n    evaluate-commands -draft -itersel %~\n        # align indent with opening paren when { is entered on a new line after the closing paren\n        try %[ execute-keys -draft h <a-F> ) M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n        # dedent standalone { after impl and related block without any { in between\n        try %@ execute-keys -draft hh <a-?> ^\\h*\\b(impl|((|pub\\ |pub\\((crate|self|super|in\\ (::)?([a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+)(::[a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+)*)\\)\\ )((async\\ |const\\ )?(unsafe\\ )?(extern\\ (\"[^\"]*\"\\ )?)?fn|struct|enum|union))|if|for)\\b <ret> <a-K> \\{ <ret> <a-semicolon> <semicolon> ll x <a-k> ^\\h*\\{$ <ret> <a-lt> @\n    ~\n]\n\ndefine-command -hidden ron-indent-on-closing %~\n    evaluate-commands -draft -itersel %_\n        # align to opening curly brace or paren when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h*[)}\\]]$ <ret> h m <a-S> 1<a-&> >\n    _\n~\n§\n"
  },
  {
    "path": "rc/filetype/ruby.kak",
    "content": "# http://ruby-lang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*(([.](rb))|(irbrc)|(pryrc)|(Brewfile)|(Capfile|[.]cap)|(Gemfile|[.]gemspec)|(Guardfile)|(Rakefile|[.]rake)|(Thorfile|[.]thor)|(Vagrantfile)) %{\n    set-option buffer filetype ruby\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=ruby %{\n    require-module ruby\n\n    set-option window static_words %opt{ruby_static_words}\n\n    hook window ModeChange pop:insert:.* -group ruby-trim-indent ruby-trim-indent\n    hook window InsertChar .* -group ruby-indent ruby-indent-on-char\n    hook window InsertChar \\n -group ruby-indent ruby-indent-on-new-line\n    hook window InsertChar \\n -group ruby-insert ruby-insert-on-new-line\n\n    alias window alt ruby-alternative-file\n\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-hooks window ruby-.+\n        unalias window alt ruby-alternative-file\n    }\n}\n\nhook -group ruby-highlight global WinSetOption filetype=ruby %{\n    add-highlighter window/ruby ref ruby\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/ruby }\n}\n\nprovide-module ruby %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/ruby regions\nadd-highlighter shared/ruby/code default-region group\nadd-highlighter shared/ruby/double_symbol region ':\"' (?<!\\\\)(\\\\\\\\)*\"                regions\nadd-highlighter shared/ruby/single_symbol region \":'\" (?<!\\\\)(\\\\\\\\)*'                fill variable\nadd-highlighter shared/ruby/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\"                 regions\nadd-highlighter shared/ruby/single_string region \"'\" (?<!\\\\)(\\\\\\\\)*'                 fill string\nadd-highlighter shared/ruby/backtick      region '(?<![$:])`' (?<!\\\\)(\\\\\\\\)*`        regions\nadd-highlighter shared/ruby/regex         region '(?<![$:])/' (?<!\\\\)(\\\\\\\\)*/[imox]* regions\nadd-highlighter shared/ruby/              region '#' '$'                             fill comment\nadd-highlighter shared/ruby/              region ^=begin ^=end                       fill comment\nadd-highlighter shared/ruby/              region -recurse \\( '%[qwQW]?\\(' \\)         fill string\nadd-highlighter shared/ruby/              region -recurse \\{ '%[qwQW]?\\{' \\}         fill string\nadd-highlighter shared/ruby/              region -recurse \\[ '%[qwQW]?\\[' \\]         fill string\nadd-highlighter shared/ruby/              region -recurse  < '%[qwQW]?<'   >         fill string\nadd-highlighter shared/ruby/              region -recurse \\( '%[isIS]\\('  \\)         fill variable\nadd-highlighter shared/ruby/              region -recurse \\{ '%[isIS]\\{'  \\}         fill variable\nadd-highlighter shared/ruby/              region -recurse \\[ '%[isIS]\\['  \\]         fill variable\nadd-highlighter shared/ruby/              region -recurse  < '%[isIS]<'    >         fill variable\nadd-highlighter shared/ruby/              region -recurse \\( '%[rxRX]\\('  \\)         fill meta\nadd-highlighter shared/ruby/              region -recurse \\{ '%[rxRX]\\{'  \\}         fill meta\nadd-highlighter shared/ruby/              region -recurse \\[ '%[rxRX]\\['  \\]         fill meta\nadd-highlighter shared/ruby/              region -recurse  < '%[rxRX]<'    >         fill meta\nadd-highlighter shared/ruby/              region -match-capture '%[qwQW]?([^\\s0-9A-Za-z\\(\\{\\[<>\\]\\}\\)])' ([^\\s0-9A-Za-z\\(\\{\\[<>\\]\\}\\)]) fill string\nadd-highlighter shared/ruby/              region -match-capture '%[isIS]([^\\s0-9A-Za-z\\(\\{\\[<>\\]\\}\\)])' ([^\\s0-9A-Za-z\\(\\{\\[<>\\]\\}\\)]) fill variable\nadd-highlighter shared/ruby/              region -match-capture '%[rxRX]([^\\s0-9A-Za-z\\(\\{\\[<>\\]\\}\\)])' ([^\\s0-9A-Za-z\\(\\{\\[<>\\]\\}\\)]) fill meta\nadd-highlighter shared/ruby/heredoc region -match-capture '<<[-~]?(?!self)(\\w+)'      '^\\h*(\\w+)$' fill string\nadd-highlighter shared/ruby/division region '[\\w\\)\\]]\\K(/|(\\h+/\\h+))' '\\w' group # Help Kakoune to better detect /…/ literals\n\n# Regular expression flags are: i → ignore case, m → multi-lines, o → only interpolate #{} blocks once, x → extended mode (ignore white spaces)\n# Literals are: i → array of symbols, q → string, r → regular expression, s → symbol, w → array of words, x → capture shell result\n\nadd-highlighter shared/ruby/double_string/ default-region fill string\nadd-highlighter shared/ruby/double_string/interpolation region -recurse \\{ \\Q#{ \\} fill meta\n\nadd-highlighter shared/ruby/double_symbol/ default-region fill variable\nadd-highlighter shared/ruby/double_symbol/interpolation region -recurse \\{ \\Q#{ \\} fill meta\n\nadd-highlighter shared/ruby/backtick/ default-region fill meta\nadd-highlighter shared/ruby/backtick/interpolation region -recurse \\{ \\Q#{ \\} fill meta\n\nadd-highlighter shared/ruby/regex/ default-region fill meta\nadd-highlighter shared/ruby/regex/interpolation region -recurse \\{ \\Q#{ \\} fill meta\n\nevaluate-commands %sh{\n    # Grammar\n    # Keywords are collected searching for keywords at\n    # https://github.com/ruby/ruby/blob/trunk/parse.y\n    keywords=\"alias|and|begin|break|case|class|def|defined|do|else|elsif|end\"\n    keywords=\"${keywords}|ensure|false|for|if|in|module|next|nil|not|or|private|protected|public|redo\"\n    keywords=\"${keywords}|rescue|retry|return|self|super|then|true|undef|unless|until|when|while|yield\"\n    attributes=\"attr_reader|attr_writer|attr_accessor\"\n    values=\"false|true|nil\"\n    meta=\"require|require_relative|include|extend\"\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list ruby_static_words ${keywords} ${attributes} ${values} ${meta}\" | tr '|' ' '\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/ruby/code/ regex \\b(${keywords})[^0-9A-Za-z_!?] 1:keyword\n        add-highlighter shared/ruby/code/ regex \\b(${attributes})\\b 0:attribute\n        add-highlighter shared/ruby/code/ regex \\b(${values})\\b 0:value\n        add-highlighter shared/ruby/code/ regex \\b(${meta})\\b 0:meta\n    \"\n}\n\nadd-highlighter shared/ruby/code/ regex \\b(\\w+:(?!:))|(:?(\\$(-[0FIKWadilpvw]|[\"'`/~&+=!$*,:.\\;<>?@\\\\])|(\\$|@@?)\\w+))|((?<!:):(![~=]|=~|>[=>]?|<((=>?)|<)?|[+\\-]@?|\\*\\*?|===?|[/`%&!^|~]|(\\w+[=?!]?)|(\\[\\]=?)))|([A-Z]\\w*|^|\\h)\\K::(?=[A-Z]) 0:variable\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command ruby-alternative-file -docstring 'Jump to the alternate file (implementation ↔ test)' %{ evaluate-commands %sh{\n    case $kak_buffile in\n        *spec/*_spec.rb)\n            altfile=$(eval echo $(echo $kak_buffile | sed s+spec/+'*'/+';'s/_spec//))\n            [ ! -f $altfile ] && echo \"fail 'implementation file not found'\" && exit\n        ;;\n        *test/*_test.rb)\n            altfile=$(eval echo $(echo $kak_buffile | sed s+test/+'*'/+';'s/_test//))\n            [ ! -f $altfile ] && echo \"fail 'implementation file not found'\" && exit\n        ;;\n        *.rb)\n            altfile=\"\"\n            altdir=\"\"\n            path=$kak_buffile\n            dirs=$(while [ $path ]; do echo $path; path=${path%/*}; done | tail -n +2)\n            for dir in $dirs; do\n                altdir=$dir/spec && suffix=spec\n                [ ! -d $altdir ] && altdir=$dir/test && suffix=test\n                if [ -d $altdir ]; then\n                    altfile=$altdir/$(realpath $kak_buffile --relative-to $dir | sed s+[^/]'*'/++';'s/.rb$/_${suffix}.rb/)\n                    break\n                fi\n            done\n            [ ! -d \"$altdir\" ] && echo \"fail 'spec/ and test/ not found'\" && exit\n        ;;\n        *)\n            echo \"fail 'alternative file not found'\" && exit\n        ;;\n    esac\n    echo \"edit $altfile\"\n}}\n\ndefine-command -hidden ruby-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden ruby-indent-on-char %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # align middle and end structures to start\n        try %{ execute-keys -draft x <a-k> ^ \\h * (else)   $ <ret> <a-a> i <a-semicolon> <a-?> ^ \\h * (if|case)                                               <ret> <a-S> 1<a-&> }\n        try %{ execute-keys -draft x <a-k> ^ \\h * (elsif)  $ <ret> <a-a> i <a-semicolon> <a-?> ^ \\h * (if)                                                    <ret> <a-S> 1<a-&> }\n        try %{ execute-keys -draft x <a-k> ^ \\h * (when)   $ <ret> <a-a> i <a-semicolon> <a-?> ^ \\h * (case)                                                  <ret> <a-S> 1<a-&> }\n        try %{ execute-keys -draft x <a-k> ^ \\h * (rescue) $ <ret> <a-a> i <a-semicolon> <a-?> ^ \\h * (begin|def)                                             <ret> <a-S> 1<a-&> }\n    }\n}\n\ndefine-command -hidden ruby-indent-on-new-line %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : ruby-trim-indent <ret> }\n        # indent after start structure\n        try %{ execute-keys -draft k x <a-k> ^ \\h * (begin|case|class|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|while|.+\\bdo$|.+\\bdo\\h\\|.+(?=\\|)) [^0-9A-Za-z_!?] <ret> j <a-gt> }\n    }\n}\n\ndefine-command -hidden ruby-insert-on-new-line %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # copy _#_ comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y jgi P }\n        # wisely add end structure\n        evaluate-commands -save-regs x %[\n            try %{ execute-keys -draft k x s ^ \\h + <ret> \\\" x y } catch %{ reg x '' }\n            try %[\n                evaluate-commands -draft %[\n                    # Check if previous line opens a block\n                    execute-keys -draft kx <a-k>^<c-r>x(begin|case|class|def|for|if|module|unless|until|while|.+\\bdo$|.+\\bdo\\h\\|.+(?=\\|))[^0-9A-Za-z_!?]<ret>\n                    # Check that we do not already have an end for this indent level which is first set via `ruby-indent-on-new-line` hook\n                    execute-keys -draft }i J x <a-K> ^<c-r>x(end|else|elsif|rescue|when)[^0-9A-Za-z_!?]<ret>\n                ]\n                execute-keys -draft o<c-r>xend<esc> # insert a new line with containing end\n            ]\n        ]\n    ]\n]\n\n§\n"
  },
  {
    "path": "rc/filetype/rust.kak",
    "content": "# http://rust-lang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](rust|rs) %{\n    set-option buffer filetype rust\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=rust %<\n    require-module rust\n    hook window ModeChange pop:insert:.* -group rust-trim-indent rust-trim-indent\n    hook window InsertChar \\n -group rust-indent rust-indent-on-new-line\n    hook window InsertChar \\{ -group rust-indent rust-indent-on-opening-curly-brace\n    hook window InsertChar [)}\\]] -group rust-indent rust-indent-on-closing\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window rust-.+ }\n>\n\nhook -group rust-highlight global WinSetOption filetype=rust %{\n    add-highlighter window/rust ref rust\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/rust }\n}\n\nprovide-module rust %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/rust regions\nadd-highlighter shared/rust/code default-region group\nadd-highlighter shared/rust/string           region %{(?<!')\"} (?<!\\\\)(\\\\\\\\)*\"              fill string\nadd-highlighter shared/rust/raw_string       region -match-capture %{(?<!')r(#*)\"} %{\"(#*)} fill string\n\nadd-highlighter shared/rust/line_doctest region ^\\h*//[!/]\\h*```($|should_panic|no_run|ignore|allow_fail|rust|test_harness|compile_fail|E\\d{4}|edition201[58]) ^\\h*//[!/]\\h*```$ regions\nadd-highlighter shared/rust/line_doctest/marker region ``` $ group\nadd-highlighter shared/rust/line_doctest/marker/fence regex ``` 0:meta\nadd-highlighter shared/rust/line_doctest/marker/keywords regex [\\d\\w] 0:meta # already matched above, just ignore comma\nadd-highlighter shared/rust/line_doctest/inner region '^\\h*//[!/]( #(?= ))?' '$| ' group\nadd-highlighter shared/rust/line_doctest/inner/comment regex //[!/] 0:documentation\nadd-highlighter shared/rust/line_doctest/inner/hidden regex '#' 0:meta\nadd-highlighter shared/rust/line_doctest/code default-region ref rust\nadd-highlighter shared/rust/line_code_rest   region ^\\h*//[!/]\\h*``` ^\\h*//[!/]\\h*```$      fill documentation # reset invalid doctest\nadd-highlighter shared/rust/line_comment2    region //[!/]{2} $                             fill comment\nadd-highlighter shared/rust/line_doc         region //[!/] $                                fill documentation\nadd-highlighter shared/rust/line_comment1    region // $                                    group\nadd-highlighter shared/rust/line_comment1/comment fill comment\nadd-highlighter shared/rust/line_comment1/todo regex (TODO|NOTE|FIXME): 1:meta\n\nadd-highlighter shared/rust/block_comment2   region -recurse /\\*\\*\\* /\\*\\*\\* \\*/            fill comment\nadd-highlighter shared/rust/block_doc        region -recurse /\\*\\* /\\*\\* \\*/ regions\nadd-highlighter shared/rust/block_doc/doctest region ```($|should_panic|no_run|ignore|allow_fail|rust|test_harness|compile_fail|E\\d{4}|edition201[58]) ```$ regions\nadd-highlighter shared/rust/block_doc/doctest/marker region ``` $ group\nadd-highlighter shared/rust/block_doc/doctest/marker/fence regex ``` 0:meta\nadd-highlighter shared/rust/block_doc/doctest/marker/keywords regex [\\d\\w] 0:meta # already matched above, just ignore comma\nadd-highlighter shared/rust/block_doc/doctest/inner default-region group\nadd-highlighter shared/rust/block_doc/doctest/inner/hidden regex '^\\h*\\**\\h*#' 0:meta\nadd-highlighter shared/rust/block_doc/doctest/inner/comment regex ^\\h*\\* 0:documentation\nadd-highlighter shared/rust/block_doc/doctest/inner/code ref rust\nadd-highlighter shared/rust/block_doc/code_rest region ``` ``` fill documentation\nadd-highlighter shared/rust/block_doc/doc    default-region fill documentation\nadd-highlighter shared/rust/block_comment1   region -recurse /\\* /\\* \\*/ group\nadd-highlighter shared/rust/block_comment1/comment fill comment\nadd-highlighter shared/rust/block_comment1/todo regex (TODO|NOTE|FIXME): 1:meta\n\nadd-highlighter shared/rust/macro_attributes region -recurse \"\\[\" \"#!?\\[\" \"\\]\" regions\nadd-highlighter shared/rust/macro_attributes/ default-region fill meta\nadd-highlighter shared/rust/macro_attributes/string region %{(?<!')\"} (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/rust/macro_attributes/raw_string region -match-capture %{(?<!')r(#*)\"} %{\"(#*)} fill string\n\nadd-highlighter shared/rust/code/operators_arithmetic   regex (\\+|-|/|\\*|=|\\^|&|\\||!|>|<|%)=? 0:operator\nadd-highlighter shared/rust/code/operators_as           regex \\bas\\b 0:operator\nadd-highlighter shared/rust/code/ref_ref                regex (&\\h+[&~@*])[^)=\\s\\t\\r\\n] 1:type\nadd-highlighter shared/rust/code/ref                    regex ([&~@*])[^)=\\s\\t\\r\\n] 1:type\nadd-highlighter shared/rust/code/operators_logic        regex &&|\\|\\| 0:operator\n\nadd-highlighter shared/rust/code/lifetime_or_loop_label regex ('([a-zA-Z]\\w+|_\\w+))\\b 1:meta\nadd-highlighter shared/rust/code/namespace              regex \\b[a-zA-Z](\\w+)?(\\h+)?(?=::) 0:module\nadd-highlighter shared/rust/code/mod_path_sep           regex :: 0:meta\nadd-highlighter shared/rust/code/question_mark          regex \\? 0:meta\n# the language keywords are defined here, but many of   them are reserved and unused yet:\n# https://doc.rust-lang.org/reference/keywords.html\nadd-highlighter shared/rust/code/function_call          regex _?[a-zA-Z]\\w*\\s*(?=\\() 0:function\nadd-highlighter shared/rust/code/generic_function_call  regex _?[a-zA-Z]\\w*\\s*(?=::<) 0:function\nadd-highlighter shared/rust/code/function_declaration   regex (?:fn\\h+)(_?\\w+)(?:<[^>]+?>)?\\( 1:function\nadd-highlighter shared/rust/code/keywords               regex \\b(?:as|break|continue|crate|else|enum|extern|false|fn|for|if|impl|in|let|loop|match|mod|pub|return|self|Self|struct|super|trait|true|type|union|unsafe|use|where|while|async|await|dyn|abstract|become|box|do|try)\\b 0:keyword\nadd-highlighter shared/rust/code/storage                regex \\b(move|mut|ref|static|const)\\b 0:type\nadd-highlighter shared/rust/code/pub_with_scope         regex \\b(pub)\\h*(\\()\\h*(crate|super|self|in\\h+[\\w:]+)\\h*(\\)) 1:keyword 2:meta 4:meta\n# after let can be an arbitrary pattern match\nadd-highlighter shared/rust/code/macro                  regex \\b\\w+! 0:meta\n# the number literals syntax is defined here:\n# https://doc.rust-lang.org/reference/tokens.html#numb  ers\nadd-highlighter shared/rust/code/values                 regex \\b(?:self|true|false|[0-9][_0-9]*(?:\\.[0-9][_0-9]*|(?:\\.[0-9][_0-9]*)?E[\\+\\-][_0-9]+)(?:f(?:32|64))?|(?:0x[_0-9a-fA-F]+|0o[_0-7]+|0b[_01]+|[0-9][_0-9]*)(?:(?:i|u|f)(?:8|16|32|64|128|size))?)\\b 0:value\nadd-highlighter shared/rust/code/char_character         regex \"'([^\\\\]|\\\\(.|x[0-9a-fA-F]{2}|u\\{[0-9a-fA-F]{1,6}\\}))'\" 0:value\n# TODO highlight error for unicode or single escape by  te character\nadd-highlighter shared/rust/code/byte_character         regex b'([\\x00-\\x5B\\x5D-\\x7F]|\\\\(.|x[0-9a-fA-F]{2}))' 0:value\nadd-highlighter shared/rust/code/builtin_types          regex \\b(?:u8|u16|u32|u64|u128|usize|i8|i16|i32|i64|i128|isize|f32|f64|bool|char|str|Self)\\b 0:type\n\nadd-highlighter shared/rust/code/enum                   regex \\b(Option|Result)\\b 0:type\nadd-highlighter shared/rust/code/enum_variant           regex \\b(Some|None|Ok|Err)\\b 0:value\nadd-highlighter shared/rust/code/std_traits             regex \\b(Copy|Send|Sized|Sync|Drop|Fn|FnMut|FnOnce|Box|ToOwned|Clone|PartialEq|PartialOrd|Eq|Ord|AsRef|AsMut|Into|From|Default|Iterator|Extend|IntoIterator|DoubleEndedIterator|ExactSizeIterator|SliceConcatExt|String|ToString|Vec)\\b 0:type\n \n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden rust-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden rust-indent-on-new-line %~\n    evaluate-commands -draft -itersel %@\n        try %{\n            try %[ # line comment\n                evaluate-commands -draft -save-regs '/\"' %[\n                    # copy the commenting prefix\n                    execute-keys -save-regs '' k x s ^\\h*//[!/]{0,2}\\h* <ret> y\n                    try %[\n                        # if the previous comment isn't empty, create a new one\n                        execute-keys x<a-K>^\\h*//[!/]{0,2}$<ret> jxs^\\h*<ret>P\n                    ] catch %[\n                        # TODO figure out a way to not delete empty comment in current line\n                        # if there is no space and text in the previous comment, remove it completely\n                        execute-keys s //.*<ret> d\n                    ]\n                ]\n            ] catch %[ # block comment\n                # if the previous line isn't within a comment scope, break\n                execute-keys -draft kx <a-k>^(\\h*/\\*|\\h+\\*(?!/))<ret>\n\n                # find comment opening, validate it was not closed, and check its using star prefixes\n                execute-keys -draft <a-?>/\\*<ret><a-H> <a-K>\\*/<ret> <a-k>\\A\\h*/\\*([^\\n]*\\n\\h*\\*)*[^\\n]*\\n\\h*.\\z<ret>\n\n                try %[\n                    # if the previous line is opening the comment, insert star preceeded by space\n                    execute-keys -draft kx<a-k>^\\h*/\\*<ret>\n                    execute-keys -draft i*<space><esc>\n                ] catch %[\n                    try %[\n                        # if the next line is a comment line insert a star\n                        execute-keys -draft jx<a-k>^\\h+\\*<ret>\n                        execute-keys -draft i*<space><esc>\n                    ] catch %[\n                        try %[\n                            # if the previous line is an empty comment line, close the comment scope\n                            execute-keys -draft kx<a-k>^\\h+\\*\\h+$<ret> x1s\\*(\\h*)<ret>c/<esc>\n                        ] catch %[\n                            # if the previous line is a non-empty comment line, add a star\n                            execute-keys -draft i*<space><esc>\n                        ]\n                    ]\n                ]\n\n                # trim trailing whitespace on the previous line\n                try %[ execute-keys -draft s\\h+$<ret> d ]\n                # align the new star with the previous one\n                execute-keys Kx1s^[^*]*(\\*)<ret>&\n            ]\n        } catch %`\n            # re-indent previous line if it starts with where to match previous block\n            # string literal parsing within extern does not handle escape\n            try %% execute-keys -draft k x <a-k> ^\\h*where\\b <ret> hh <a-?> ^\\h*\\b(impl|((|pub\\ |pub\\((crate|self|super|in\\ (::)?([a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+)(::[a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+)*)\\)\\ )((async\\ |const\\ )?(unsafe\\ )?(extern\\ (\"[^\"]*\"\\ )?)?fn|struct|enum|union)))\\b <ret> <a-S> 1<a-&> %\n            # preserve previous line indent\n            try %{ execute-keys -draft <semicolon> K <a-&> }\n            # indent after lines ending with [{([].+ and move first parameter to own line\n            try %< execute-keys -draft [c[({[],[)}\\]] <ret> <a-k> \\A[({[][^\\n]+\\n[^\\n]*\\n?\\z <ret> L i<ret><esc> <gt> <a-S> <a-&> >\n            # indent after non-empty lines not starting with operator and not ending with , or ; or {\n            # XXX simplify this into a single <a-k> without s\n            try %< execute-keys -draft k x s [^\\h].+ <ret> <a-K> \\A[-+*/&|^})<gt><lt>#] <ret> <a-K> [,<semicolon>{](\\h*/[/*].*|)$ <ret> j <a-gt> >\n            # indent after lines ending with {\n            try %+ execute-keys -draft k x <a-k> \\{$ <ret> j <a-gt> +\n            # dedent after lines starting with . and ending with } or ) or , or ; or .await (} or ) or .await maybe with ?)\n            try %_ execute-keys -draft k x <a-k> ^\\h*\\. <ret> <a-k> ([,<semicolon>]|(([})]|\\.await)\\?*))\\h*$ <ret> j <a-lt> _\n            # dedent after lines ending with \" => {}\" - part of empty match\n            try %# execute-keys -draft k x <a-k> \\ =>\\ \\{\\}\\h*$ <ret> j <a-lt> #\n            # align to opening curly brace or paren when newline is inserted before a single closing\n            try %< execute-keys -draft <a-h> <a-k> ^\\h*[)}] <ret> h m <a-S> 1<a-&> >\n            # todo dedent additional unmatched parenthesis\n            # try %& execute-keys -draft k x s \\((?:[^)(]+|\\((?:[^)(]+|\\([^)(]*\\))*\\))*\\) l Gl s\\) %sh{\n                # count previous selections length\n                # printf \"j $(echo $kak_selections_length | wc -w) <a-lt>\"\n            # } &\n        `\n        # filter previous line\n        try %{ execute-keys -draft k : rust-trim-indent <ret> }\n    @\n~\n\ndefine-command -hidden rust-indent-on-opening-curly-brace %[\n    evaluate-commands -draft -itersel %~\n        # align indent with opening paren when { is entered on a new line after the closing paren\n        try %[ execute-keys -draft h <a-F> ) M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n        # dedent standalone { after impl and related block without any { in between\n        try %@ execute-keys -draft hh <a-?> ^\\h*\\b(impl|((|pub\\ |pub\\((crate|self|super|in\\ (::)?([a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+)(::[a-zA-Z][a-zA-Z0-9_]*|_[a-zA-Z0-9_]+)*)\\)\\ )((async\\ |const\\ )?(unsafe\\ )?(extern\\ (\"[^\"]*\"\\ )?)?fn|struct|enum|union))|if|for)\\b <ret> <a-K> \\{ <ret> <a-semicolon> <semicolon> ll x <a-k> ^\\h*\\{$ <ret> <a-lt> @\n    ~\n]\n\ndefine-command -hidden rust-indent-on-closing %~\n    evaluate-commands -draft -itersel %_\n        # align to opening curly brace or paren when alone on a line\n        try %< execute-keys -draft <a-h> <a-k> ^\\h*[)}\\]]$ <ret> h m <a-S> 1<a-&> >\n    _\n~\n\n§\n"
  },
  {
    "path": "rc/filetype/sass.kak",
    "content": "# http://sass-lang.com\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](sass) %{\n    set-option buffer filetype sass\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=sass %<\n    require-module sass\n\n    hook window ModeChange pop:insert:.* -group sass-trim-indent sass-trim-indent\n    hook window InsertChar \\} -group sass-indent sass-indent-on-closing-brace\n    hook window InsertChar \\n -group sass-insert sass-insert-on-new-line\n    hook window InsertChar \\n -group sass-indent sass-indent-on-new-line\n    set-option buffer extra_word_chars '_' '-'\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window sass-.+ }\n>\n\nhook -group sass-highlight global WinSetOption filetype=sass %{\n    add-highlighter window/sass ref sass\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/sass }\n}\n\n\nprovide-module sass %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/sass regions\nadd-highlighter shared/sass/code default-region group\nadd-highlighter shared/sass/single_string  region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/sass/double_string  region \"'\" \"'\"             fill string\nadd-highlighter shared/sass/comment        region '//' '$'            fill comment\nadd-highlighter shared/sass/css_comment    region /[*] [*]/           fill comment\n\nadd-highlighter shared/sass/code/ regex [*]|[#.][A-Za-z][A-Za-z0-9_-]* 0:variable\nadd-highlighter shared/sass/code/ regex &|@[A-Za-z][A-Za-z0-9_-]* 0:meta\nadd-highlighter shared/sass/code/ regex (#[0-9A-Fa-f]+)|((\\d*\\.)?\\d+(em|px)) 0:value\nadd-highlighter shared/sass/code/ regex ([A-Za-z][A-Za-z0-9_-]*)\\h*: 1:keyword\nadd-highlighter shared/sass/code/ regex :(before|after) 0:attribute\nadd-highlighter shared/sass/code/ regex !important 0:keyword\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden sass-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden sass-indent-on-closing-brace %<\n    evaluate-commands -draft -itersel %<\n        # align closing brace to same indentation as the line that the opening brace resides on\n        try %[ execute-keys -draft <a-h> <a-k> ^\\h+\\}$ <ret> m <a-S> 1<a-&> ]\n    >\n>\n\ndefine-command -hidden sass-insert-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # copy // comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K/{2,}\\h* <ret> y gh j P }\n    >\n>\n\ndefine-command -hidden sass-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : sass-trim-indent <ret> }\n        # avoid indent after properties and comments\n        try %{ execute-keys -draft k x <a-K> [:/] <ret> j <a-gt> }\n        # deindent closing brace when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> ]\n    >\n>\n\n§\n"
  },
  {
    "path": "rc/filetype/scala.kak",
    "content": "# http://scala-lang.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](scala|sbt|sc) %{\n    set-option buffer filetype scala\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=scala %[\n    require-module scala\n\n    hook window ModeChange pop:insert:.* -group scala-trim-indent scala-trim-indent\n    hook window InsertChar \\n -group scala-insert scala-insert-on-new-line\n    hook window InsertChar \\n -group scala-indent scala-indent-on-new-line\n    hook window InsertChar \\} -group scala-indent scala-indent-on-closing-curly-brace\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window scala-.+ }\n]\n\nhook -group scala-highlight global WinSetOption filetype=scala %{\n    add-highlighter window/scala ref scala\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/scala }\n}\n\n\nprovide-module scala %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/scala regions\nadd-highlighter shared/scala/code default-region group\nadd-highlighter shared/scala/string       region '\"' (?<!\\\\)(\\\\\\\\)*\"   fill string\nadd-highlighter shared/scala/literal      region `    `                fill variable\nadd-highlighter shared/scala/comment      region -recurse /[*] /[*] [*]/  fill comment\nadd-highlighter shared/scala/line_comment region //   $                fill comment\n\n# Keywords are collected at\n# http://tutorialspoint.com/scala/scala_basic_syntax.htm\n\nadd-highlighter shared/scala/code/ regex (?:\\b|\\W)(@\\w+|import|package)\\b 0:meta\nadd-highlighter shared/scala/code/ regex \\b(true|false|null)\\b 0:value\nadd-highlighter shared/scala/code/ regex \\b(?:class|extends|with)\\s+(\\w+) 0:type\nadd-highlighter shared/scala/code/ regex \\b([A-Z]\\w*)\\b 0:type\nadd-highlighter shared/scala/code/ regex (?:def|var|val)\\s+(\\w+) 0:variable\nadd-highlighter shared/scala/code/ regex \\b(become|case|catch|class|def|do|else|extends|final|finally|for|forSome|goto|if|initialize|macro|match|new|object|onTransition|return|startWith|stay|this|super|throw|trait|try|unbecome|using|val|var|when|while|with|yield)\\b 0:keyword\nadd-highlighter shared/scala/code/ regex \\b(abstract|final|implicit|implicitly|lazy|override|private|protected|require|sealed)\\b 0:attribute\nadd-highlighter shared/scala/code/ regex (\\[|\\]|=>|<:|:>|=:=|::|&&|\\|\\|) 0:operator\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden scala-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden scala-insert-on-new-line %[\n    evaluate-commands -draft -itersel %[\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K#\\h* <ret> y<c-o>P<esc> }\n    ]\n]\n\ndefine-command -hidden scala-indent-on-new-line %[\n    evaluate-commands -draft -itersel %[\n        # preserve previous line indent\n        try %[ execute-keys -draft <semicolon> K <a-&> ]\n        # filter previous line\n        try %[ execute-keys -draft k : scala-trim-indent <ret> ]\n        # indent after lines ending with {\n        try %[ execute-keys -draft k x <a-k> \\{$ <ret> j <a-gt> ]\n        # deindent closing brace when after cursor\n        try %[ execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> ]\n    ]\n]\n\ndefine-command -hidden scala-indent-on-closing-curly-brace %[\n    evaluate-commands -draft -itersel %[\n        # align to opening curly brace when alone on a line\n        try %[ execute-keys -draft <a-h> <a-k> ^\\h+\\}$ <ret> m s \\A|.\\z <ret> 1<a-&> ]\n    ]\n]\n\n]\n"
  },
  {
    "path": "rc/filetype/scheme.kak",
    "content": "# http://www.scheme-reports.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate (.*/)?(.*\\.(scm|ss|sld|sps|sls)) %{\n    set-option buffer filetype scheme\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=scheme %{\n    require-module scheme\n\n    set-option window static_words %opt{scheme_static_words}\n\n    set-option buffer extra_word_chars '!' '$' '%' '&' '*' '+' '-' '.' '/' ':' '<' '=' '>' '?' '@' '^' '_' '~'\n    hook window ModeChange pop:insert:.* -group scheme-trim-indent lisp-trim-indent\n    hook window InsertChar \\n -group scheme-indent lisp-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window scheme-.+ }\n}\n\nhook -group scheme-highlight global WinSetOption filetype=scheme %{\n    add-highlighter window/scheme ref scheme\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/scheme }\n}\n\nprovide-module scheme %{\n\nrequire-module lisp\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/scheme regions\nadd-highlighter shared/scheme/code default-region group\n\nadd-highlighter shared/scheme/string region %{(?<!#\\\\)\"} (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/scheme/comment region %{(?<!#\\\\);} '$' fill comment\nadd-highlighter shared/scheme/comment-form region -recurse \"\\(\" \"#;\\(\" \"\\)\" fill comment\nadd-highlighter shared/scheme/comment-block region \"#\\|\" \"\\|#\" fill comment\n\nadd-highlighter shared/scheme/code/ regex (#t|#f) 0:value\n\n# Numbers\nadd-highlighter shared/scheme/code/ regex '(#[bB]#[eiEI]|#[eiEI]#[bB]|#[bB])[01]+' 0:value\nadd-highlighter shared/scheme/code/ regex '(#[oO]#[eiEI]|#[eiEI]#[oO]|#[oO])[0-7]+' 0:value\nadd-highlighter shared/scheme/code/ regex '(#[dD](#[eiEI])?|#[eiEI]#[dD]|#[eiEI])(\\d+(?:\\.\\d*)?|\\.\\d+)([esfdlESFDL][-+]?\\d+)?' 0:value\nadd-highlighter shared/scheme/code/ regex '(#[xX]#[eiEI]|#[eiEI]#[xX]|#[xX])[0-9a-fA-F]+' 0:value\n\nadd-highlighter shared/scheme/code/ regex (#\\\\((\\w+)|(.))) 0:value\n\nadd-highlighter shared/scheme/code/ regex '#!(?:no-)?fold-case\\b' 0:meta\n\nevaluate-commands %sh{ exec awk -f - <<'EOF'\n    BEGIN {\n        split(\"and begin call-with-current-continuation call/cc case case-lambda cond define \"\\\n              \"define-record-type define-values delay delay-force do else guard if lambda \"\\\n              \"let let* let-values let*-values letrec letrec* or set! unless when\", keywords);\n\n        # Macro expressions, imports/exports/library\n        split(\"begin-syntax cond-expand define-library define-syntax export import include \"\\\n              \"include-ci include-library-declarations let-syntax letrec-syntax quote \"\\\n              \"quasiquote syntax-rules syntax-case unquote unquote-splicing\", meta);\n\n        # Basic operators.\n        split(\"* + - ... / < <= = => > >=\", operators);\n\n        # Procedures that create a base type and their predicates (for easier type checking)\n        split(\"list vector bytevector cons string boolean? list? pair? vector? bytevector? \"\\\n              \"string? char? complex? eof-object eof-object? input-port? null? number? \"\\\n              \"output-port? port? procedure? symbol?\", types);\n\n        # R7RS available procedures\n        split(\"abs acos angle append apply asin assoc assq assv atan boolean=? \"\\\n              \"bytevector-append bytevector-copy bytevector-copy! bytevector-length \"\\\n              \"bytevector-u8-ref bytevector-u8-set! caaaar caaadr caaar caadar caaddr caadr \"\\\n              \"caar cadaar cadadr cadar caddar cadddr caddr cadr call-with-input-file \"\\\n              \"call-with-output-file call-with-values car cdaaar cdaadr cdaar cdadar cdaddr \"\\\n              \"cdadr cdar cddaar cddadr cddar cdddar cddddr cdddr cddr cdr ceiling \"\\\n              \"char->integer char-alphabetic? char-ci<=? char-ci<? char-ci=? char-ci>=? \"\\\n              \"char-ci>? char-downcase char-foldcase char-lower-case? char-numeric? \"\\\n              \"char-ready? char-upcase char-upper-case? char-whitespace? char<=? char<? \"\\\n              \"char=? char>=? char>? close-input-port close-output-port close-port cons cos \"\\\n              \"current-input-port current-output-port denominator digit-value display \"\\\n              \"dynamic-wind eq? equal? eqv? error error-object-irritants \"\\\n              \"error-object-message error-object? eval even? exact exact->inexact \"\\\n              \"exact-integer? exact-integer-sqrt exact? exp expt features file-error? \"\\\n              \"finite? floor floor/ floor-quotient floor-remainder for-each force \"\\\n              \"flush-output-port gcd get-output-bytevector get-output-string guard imag-part \"\\\n              \"inexact->exact inexact inexact? infinite? input-port-open? integer->char \"\\\n              \"integer? interaction-environment lcm length list list-copy list-set! \"\\\n              \"list->string list->vector list-ref list-tail load log magnitude \"\\\n              \"make-bytevector make-list make-parameter make-polar make-promise \"\\\n              \"make-rectangular make-string make-vector map max member memq memv min modulo \"\\\n              \"nan? negative? newline not null-environment number->string numerator odd? \"\\\n              \"open-input-bytevector open-input-file open-input-string \"\\\n              \"open-output-bytevector open-output-file open-output-string output-port-open? \"\\\n              \"or parameterize peek-char peek-u8 positive? promise? quotient raise \"\\\n              \"raise-continuable rational? rationalize read read-bytevector read-bytevector! \"\\\n              \"read-char read-error? read-line read-string read-u8 real-part real? remainder \"\\\n              \"reverse round scheme-report-environment set-car! set-cdr! sin square sqrt \"\\\n              \"string->list string->number string->symbol string->utf8 string->vector \"\\\n              \"string-append string-ci<=? string-ci<? string-ci=? string-ci>=? string-ci>? \"\\\n              \"string-copy string-copy! string-downcase string-fill! string-foldcase \"\\\n              \"string-for-each string-length string-map string-ref string-set! string-upcase \"\\\n              \"string<=? string<? string=? string>=? string>? substring symbol=? \"\\\n              \"symbol->string syntax-error tan textual-port? truncate truncate/ \"\\\n              \"truncate-quotient truncate-remainder u8-ready? unless utf8->string values \"\\\n              \"vector vector->list vector->string vector-append vector-copy vector-copy! \"\\\n              \"vector-for-each vector-fill! vector-length vector-map vector-ref vector-set! \"\\\n              \"when with-exception-handler with-input-from-file with-output-to-file write \"\\\n              \"write-bytevector write-char write-string write-u8 zero?\", builtins);\n\n        non_word_chars=\"['\\\"\\\\s\\\\(\\\\)\\\\[\\\\]\\\\{\\\\};]\";\n\n        normal_identifiers=\"-!$%&\\\\*\\\\+\\\\./:<=>\\\\?@\\\\^_~a-zA-Z0-9\";\n        identifier_chars=\"[\" normal_identifiers \"][\" normal_identifiers \",#]*\";\n    }\n    function kak_escape(s) {\n        gsub(/'/, \"''\", s);\n        return \"'\" s \"'\";\n    }\n    function add_highlighter(regex, highlight) {\n        printf(\"add-highlighter shared/scheme/code/ regex %s %s\\n\", kak_escape(regex), highlight);\n    }\n    function quoted_join(words, quoted, first) {\n        first=1\n        for (i in words) {\n            if (!first) { quoted=quoted \"|\"; }\n            quoted=quoted \"\\\\Q\" words[i] \"\\\\E\";\n            first=0;\n        }\n        return quoted;\n    }\n    function add_word_highlighter(words, face, regex) {\n        regex = \"(?<![\" normal_identifiers \"])(\" quoted_join(words) \")(?![\" normal_identifiers \"])\";\n        add_highlighter(regex, \"1:\" face);\n    }\n    function print_words(words) {\n        for (i in words) { printf(\" %s\", words[i]); }\n    }\n\n    BEGIN {\n        printf(\"declare-option str-list scheme_static_words \");\n        print_words(keywords); print_words(meta); print_words(operators); print_words(builtins);\n        printf(\"\\n\");\n\n        add_word_highlighter(keywords, \"keyword\");\n        add_word_highlighter(meta, \"meta\");\n        add_word_highlighter(operators, \"operator\");\n        add_word_highlighter(builtins, \"function\");\n        add_word_highlighter(types, \"type\");\n        add_highlighter(non_word_chars \"+('\" identifier_chars \")\", \"1:attribute\");\n        add_highlighter(\"\\\\(define\\\\W+\\\\((\" identifier_chars \")\", \"1:function\");\n        add_highlighter(\"\\\\(define\\\\W+(\" identifier_chars \")\\\\W+\\\\(lambda\", \"1:function\");\n\n        # unprefixed decimals\n        add_highlighter(\"(?<![\" normal_identifiers \"])(\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)(?:[esfdlESFDL][-+]?\\\\d+)?(?![\" normal_identifiers \"])\", \"0:value\");\n        # inf and nan\n        add_highlighter(\"(?<![\" normal_identifiers \"])[+-](?:inf|nan)\\\\.0(?![\" normal_identifiers \"])\", \"0:value\");\n    }\nEOF\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/scss.kak",
    "content": "# http://sass-lang.com\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](scss) %{\n    set-option buffer filetype scss\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=scss %[\n    require-module scss\n\n    hook window ModeChange pop:insert:.* -group scss-trim-indent scss-trim-indent\n    hook window InsertChar \\n -group scss-indent scss-insert-on-new-line\n    hook window InsertChar \\n -group scss-indent scss-indent-on-new-line\n    hook window InsertChar \\} -group scss-indent scss-indent-on-closing-curly-brace\n    set-option buffer extra_word_chars '_' '-'\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window scss-.+ }\n]\n\nhook -group scss-highlight global WinSetOption filetype=scss %{\n    add-highlighter window/scss ref scss\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/scss }\n}\n\n\nprovide-module scss %[\n\nrequire-module css\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/scss regions\nadd-highlighter shared/scss/core default-region group\nadd-highlighter shared/scss/comment region ^\\h*// $ fill comment\n\nadd-highlighter shared/scss/core/ ref css\nadd-highlighter shared/scss/core/ regex & 0:keyword\nadd-highlighter shared/scss/core/ regex \\$[A-Za-z][A-Za-z0-9_-]* 0:variable\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden scss-trim-indent      css-trim-indent\ndefine-command -hidden scss-insert-on-new-line            css-insert-on-new-line\ndefine-command -hidden scss-indent-on-new-line            css-indent-on-new-line\ndefine-command -hidden scss-indent-on-closing-curly-brace css-indent-on-closing-curly-brace\n\n]\n"
  },
  {
    "path": "rc/filetype/sh.kak",
    "content": "hook global BufCreate .*\\.((z|ba|c|k|mk)?(sh(rc|_profile|env)?|profile)) %{\n    set-option buffer filetype sh\n}\n\nhook global WinSetOption filetype=sh %{\n    require-module sh\n    set-option window static_words %opt{sh_static_words}\n\n    hook window ModeChange pop:insert:.* -group sh-trim-indent sh-trim-indent\n    hook window InsertChar \\n -group sh-insert sh-insert-on-new-line\n    hook window InsertChar \\n -group sh-indent sh-indent-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window sh-.+ }\n}\n\nhook -group sh-highlight global WinSetOption filetype=sh %{\n    add-highlighter window/sh ref sh\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/sh }\n}\n\n# using non-ascii characters here so that we can use the '[' command\nprovide-module sh %§\n\nadd-highlighter shared/sh regions\nadd-highlighter shared/sh/code default-region group\nadd-highlighter shared/sh/arithmetic region -recurse \\(.*?\\( (\\$|(?<=for)\\h*)\\(\\( \\)\\) group\nadd-highlighter shared/sh/double_string region  %{(?<!\\\\)(?:\\\\\\\\)*\\K\"} %{(?<!\\\\)(?:\\\\\\\\)*\"} group\nadd-highlighter shared/sh/single_string region %{(?<!\\\\)(?:\\\\\\\\)*\\K'} %{'} fill string\nadd-highlighter shared/sh/expansion region -recurse (?<!\\\\)(?:\\\\\\\\)*\\K\\$\\{ (?<!\\\\)(?:\\\\\\\\)*\\K\\$\\{ \\}|\\n fill value\nadd-highlighter shared/sh/comment region (?<!\\\\)(?:\\\\\\\\)*(?:^|\\h)\\K# '$' fill comment\nadd-highlighter shared/sh/heredoc region -match-capture '<<-?\\h*''?(\\w+)''?' '^\\t*(\\w+)$' fill string\n\nadd-highlighter shared/sh/arithmetic/expansion ref sh/double_string/expansion\nadd-highlighter shared/sh/double_string/fill fill string\n\nevaluate-commands %sh{\n    # Grammar\n    # Generated with `compgen -k` in bash\n    keywords=\"if then else elif fi case esac for select while until do done in\n             function time coproc\"\n\n    # Generated with `compgen -b` in bash\n    builtins=\"alias bg bind break builtin caller cd command compgen complete\n             compopt continue declare dirs disown echo enable eval exec\n             exit export false fc fg getopts hash help history jobs kill\n             let local logout mapfile popd printf pushd pwd read readarray\n             readonly return set shift shopt source suspend test times trap\n             true type typeset ulimit umask unalias unset wait\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list sh_static_words $(join \"${keywords}\" ' ') $(join \"${builtins}\" ' ')\"\n\n    # Highlight keywords\n    printf %s\\\\n \"add-highlighter shared/sh/code/ regex (?<!-)\\b($(join \"${keywords}\" '|'))\\b(?!-) 0:keyword\"\n\n    # Highlight builtins\n    printf %s \"add-highlighter shared/sh/code/builtin regex (?<!-)\\b($(join \"${builtins}\" '|'))\\b(?!-) 0:builtin\"\n}\n\nadd-highlighter shared/sh/code/operators regex [\\[\\]\\(\\)&|]{1,2} 0:operator\nadd-highlighter shared/sh/code/variable regex ((?<![-:])\\b\\w+)= 1:variable\nadd-highlighter shared/sh/code/alias regex \\balias(\\h+[-+]\\w)*\\h+([\\w-.]+)= 2:variable\nadd-highlighter shared/sh/code/function regex ^\\h*(\\S+(?<!=))\\h*\\(\\) 1:function\n\nadd-highlighter shared/sh/code/unscoped_expansion regex (?<!\\\\)(?:\\\\\\\\)*\\K\\$(\\w+|#|@|\\?|\\$|!|-|\\*) 0:value\nadd-highlighter shared/sh/double_string/expansion regex (?<!\\\\)(?:\\\\\\\\)*\\K\\$(\\w+|#|@|\\?|\\$|!|-|\\*|\\{.+?\\}) 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden sh-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\n# This is at best an approximation, since shell syntax is very complex.\n# Also note that this targets plain sh syntax, not bash - bash adds a whole\n# other level of complexity. If your bash code is fairly portable this will\n# probably work.\n#\n# Of necessity, this is also fairly opinionated about indentation styles.\n# Doing it \"properly\" would require far more context awareness than we can\n# bring to this kind of thing.\ndefine-command -hidden sh-insert-on-new-line %[\n    evaluate-commands -draft -itersel %[\n        # copy '#' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n    ]\n]\n\n# Use custom object matching to copy indentation for the various logical\n# blocks.\n#\n# Note that we're using a weird non-ascii character instead of [ or { here\n# because the '[' and '{' characters need to be available for the commands.\ndefine-command -hidden sh-indent-on-new-line %¶\n    evaluate-commands -draft -itersel %@\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : sh-trim-indent <ret> }\n\n        # Indent loop syntax, e.g.:\n        # for foo in bar; do\n        #       things\n        # done\n        #\n        # or:\n        #\n        # while foo; do\n        #       things\n        # done\n        #\n        # or equivalently:\n        #\n        # while foo\n        # do\n        #       things\n        # done\n        #\n        # indent after do\n        try %{ execute-keys -draft , k x <a-k> \\bdo$ <ret> j <a-gt> }\n        # copy the indentation of the matching for/when - matching on the do\n        # statement, so we don't need to duplicate this for the two loop\n        # structures.\n        try %{ execute-keys -draft , k x <a-k> \\bdone$ <ret> gh [c\\bdo\\b,\\bdone\\b <ret> x <a-S> 1<a-&> , j K <a-&> }\n\n        # Indent if/then/else syntax, e.g.:\n        # if [ $foo = $bar ]; then\n        #       things\n        # else\n        #       other_things\n        # fi\n        #\n        # or equivalently:\n        # if [ $foo = $bar ]\n        # then\n        #       things\n        # else\n        #       other_things\n        # fi\n        #\n        # indent after then\n        try %{ execute-keys -draft , k x <a-k> \\bthen$ <ret> j <a-gt> }\n        # copy the indentation of the matching if\n        try %{ execute-keys -draft , k x <a-k> \\bfi$ <ret> gh [c\\bif\\b,\\bfi\\b <ret> x <a-S> 1<a-&> , j K <a-&> }\n        # copy the indentation of the matching if, and then re-indent afterwards\n        try %{ execute-keys -draft , k x <a-k> \\belse$ <ret> gh [c\\bif\\b,\\bfi\\b <ret> x <a-S> 1<a-&> , j K <a-&> j <a-gt> }\n\n        # Indent case syntax, e.g.:\n        # case \"$foo\" in\n        #       bar) thing1;;\n        #       baz)\n        #               things\n        #               ;;\n        #       *)\n        #               default_things\n        #               ;;\n        # esac\n        #\n        # or equivalently:\n        # case \"$foo\"\n        # in\n        #       bar) thing1;;\n        # esac\n        #\n        # indent after in\n        try %{ execute-keys -draft , k x <a-k> \\bin$ <ret> j <a-gt> }\n        # copy the indentation of the matching case\n        try %{ execute-keys -draft , k x <a-k> \\besac$ <ret> gh [c\\bcase\\b,\\besac\\b <ret> x <a-S> 1<a-&> , j K <a-&> }\n        # indent after )\n        try %{ execute-keys -draft , k x <a-k> ^\\s*\\(?[^(]+[^)]\\)$ <ret> j <a-gt> }\n        # deindent after ;;\n        try %{ execute-keys -draft , k x <a-k> ^\\s*\\;\\;$ <ret> j <a-lt> }\n\n        # Indent compound commands as logical blocks, e.g.:\n        # {\n        #       thing1\n        #       thing2\n        # }\n        #\n        # or in a function definition:\n        # foo () {\n        #       thing1\n        #       thing2\n        # }\n        #\n        # We don't handle () delimited compond commands - these are technically very\n        # similar, but the use cases are quite different and much less common.\n        #\n        # Note that in this context the '{' and '}' characters are reserved\n        # words, and hence must be surrounded by a token separator - typically\n        # white space (including a newline), though technically it can also be\n        # ';'. Only vertical white space makes sense in this context, though,\n        # since the syntax denotes a logical block, not a simple compound command.\n        try %= execute-keys -draft , k x <a-k> (\\s|^)\\{$ <ret> j <a-gt> =\n        # deindent closing }\n        try %= execute-keys -draft , k x <a-k> ^\\s*\\}$ <ret> <a-lt> j K <a-&> =\n        # deindent closing } when after cursor\n        try %= execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> =\n\n    @\n¶\n\n§\n"
  },
  {
    "path": "rc/filetype/sml.kak",
    "content": "# https://smlfamily.github.io\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.(sml|fun|sig) %{\n    set-option buffer filetype sml\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=sml %{\n    require-module sml\n    set-option buffer extra_word_chars '_' \"'\"\n    set-option window static_words %opt{sml_static_words}\n}\n\nhook -group sml-highlight global WinSetOption filetype=sml %{\n    add-highlighter window/sml ref sml\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/sml }\n}\n\nprovide-module sml %[\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/sml regions\nadd-highlighter shared/sml/code default-region group\nadd-highlighter shared/sml/string region '#?\"' '(?<!\\\\)(\\\\\\\\)*\"' fill string\nadd-highlighter shared/sml/comment region -recurse '\\(\\*' '\\(\\*' '\\*\\)' fill comment\n\nevaluate-commands %sh{\n    keywords='abstype and andalso as case datatype do else end exception fn fun\n              handle if in infix infixr let local nonfix of op open orelse raise\n              rec then type val with withtype while eqtype functor include\n              sharing sig signature struct structure'\n    types='unit exn ref'\n    ops='before ignore o\n         div mod quot rem abs\n         not chr ord ceil floor round trunc\n         andb orb xorb notb'\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    printf %s\\\\n \"declare-option str-list sml_static_words $(join \"${keywords} ${types} ${ops}\" ' ')\"\n\n    printf %s \"\n        add-highlighter shared/sml/code/ regex \\b($(join \"${keywords}\" '|'))\\b 0:keyword\n        add-highlighter shared/sml/code/ regex \\b($(join \"${types}\" '|'))\\b 0:builtin\n        add-highlighter shared/sml/code/ regex \\b($(join \"${ops}\" '|'))\\b 0:operator\n    \"\n}\n\n# Symbolic identifiers\nadd-highlighter shared/sml/code/ regex \"[!*/+\\-~\\^@=<>%%&$?`\\\\#:|]+\" 0:operator\n\n# Record projection functions\nadd-highlighter shared/sml/code/ regex \"(?<![!*/+\\-~\\^@=<>%%&$?`\\\\#:|])#([\\w']+)?(?![!*/+\\-~\\^@=<>%%&$?`\\\\#:|])\" 0:function\n\n# Symbolic keywords\nadd-highlighter shared/sml/code/ regex \"(?<![!*/+\\-~\\^@=<>%%&$?`\\\\#:|])(=>|=|\\*|->|:>|:|;|\\.\\.\\.|\\b_\\b|\\|)(?![!*/+\\-~\\^@=<>%%&$?`\\\\#:|])\" 0:keyword\n\n# Type variables\nadd-highlighter shared/sml/code/ regex \"(?<![\\w'])'[\\w']+(?![\\w'])\" 0:variable\n\n# Structure identifiers and value constructors\nadd-highlighter shared/sml/code/ regex \"(?<![\\w'])([A-Z][\\w']*\\.?)\" 0:type\n\n# Signature identifiers and all-caps value constructors\nadd-highlighter shared/sml/code/ regex \"(?<![\\w'])[A-Z]{2}[A-Z0-9_']+(?![\\w'])\" 0:attribute\n\n# Constants\nadd-highlighter shared/sml/code/ regex \"(?<![\\w'])(true|false|nil)\\b\" 0:value\n\n# Numeric literals\nadd-highlighter shared/sml/code/ regex \"(?<![\\w'])0w[0-9]+\\b\" 0:value\nadd-highlighter shared/sml/code/ regex \"(?<![\\w'])(0wx|0xw)[0-9a-fA-F]+\\b\" 0:value\nadd-highlighter shared/sml/code/ regex \"(?<![\\w'])(0wb|0bw)[01]+\\b\" 0:value\nadd-highlighter shared/sml/code/ regex \"(~|(?<![\\w']))0x[0-9a-fA-F]+\\b\" 0:value\nadd-highlighter shared/sml/code/ regex \"(~|(?<![\\w']))0b[01]+\\b\" 0:value\nadd-highlighter shared/sml/code/ regex \"(?<!#)(~|(?<![\\w']))[0-9]+(\\.[0-9]+)?([eE]~?[0-9]+)?\\b\" 0:value\n\n]\n"
  },
  {
    "path": "rc/filetype/sql.kak",
    "content": "# https://www.w3schools.com/sql/default.asp\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](?i)sql %{\n    set-option buffer filetype sql\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=sql %{\n    require-module sql\n    set-option window static_words %opt{sql_static_words}\n}\n\nhook -group sql-highlight global WinSetOption filetype=sql %{\n    add-highlighter window/sql ref sql\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/sql }\n}\n\n\nprovide-module sql %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/sql regions\nadd-highlighter shared/sql/code default-region group\nadd-highlighter shared/sql/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/sql/single_string region \"'\" (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/sql/comment1 region '--' '$' fill comment\nadd-highlighter shared/sql/comment2 region '#' '$' fill comment\nadd-highlighter shared/sql/comment3 region '/\\*' '\\*/' fill comment\n\nevaluate-commands %sh{\n    # Keywords\n    keywords=\"ALTER|AS|ASC|AUTO_INCREMENT|CHECK|CONSTRAINT|CREATE|DATABASE|DEFAULT|DELETE|DESC|DISTINCT|DROP\"\n    keywords=\"${keywords}|EXISTS|FOREIGN KEY|FROM|FULL JOIN|FULL OUTER JOIN|GROUP BY|HAVING|INDEX|INNER JOIN\"\n    keywords=\"${keywords}|INSERT INTO|INTO|JOIN|LEFT JOIN|LEFT OUTER JOIN|LIMIT|MODIFY|NOT NULL|ON|ORDER BY|PRIMARY KEY\"\n    keywords=\"${keywords}|REFERENCES|RIGHT JOIN|RIGHT OUTER JOIN|SELECT|SELECT TOP|SET|TABLE|TRUNCATE|UNION|UNIQUE\"\n    keywords=\"${keywords}|UPDATE|VALUES|VIEW|WHERE\"\n\n    # Operators\n    operators=\"ALL|AND|ANY|BETWEEN|EXISTS|IN|IS|LIKE|NOT|OR|SOME\"\n\n    # MySQL functions\n    functions=\"ABS|ACOS|ADDDATE|ADDTIME|ASCII|ASIN|ATAN|AVG|BIN|BINARY|CASE|CAST|CEIL|CEILING\"\n    functions=\"${functions}|CHARACTER_LENGTH|CHAR_LENGTH|COALESCE|CONCAT|CONCAT_WS|CONNECTION_ID|CONV|CONVERT\"\n    functions=\"${functions}|COS|COT|COUNT|CURDATE|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER\"\n    functions=\"${functions}|CURTIME|DATABASE|DATE|DATE_ADD|DATEDIFF|DATE_FORMAT|DATE_SUB|DAY|DAYNAME\"\n    functions=\"${functions}|DAYOFMONTH|DAYOFWEEK|DAYOFYEAR|DEGREES|DIV|EXP|EXTRACT|FIELD|FIND_IN_SET|FLOOR\"\n    functions=\"${functions}|FORMAT|FROM_DAYS|GREATEST|HOUR|IF|IFNULL|INSERT|INSTR|ISNULL|LAST_DAY\"\n    functions=\"${functions}|LAST_INSERT_ID|LCASE|LEAST|LEFT|LENGTH|LN|LOCALTIME|LOCALTIMESTAMP|LOCATE|LOG\"\n    functions=\"${functions}|LOWER|LPAD|LTRIM|MAKEDATE|MAKETIME|MAX|MICROSECOND|MID|MIN|MINUTE|MOD|MONTH\"\n    functions=\"${functions}|MONTHNAME|NOW|NULLIF|PERIOD_ADD|PERIOD_DIFF|PI|POSITION|POW|POWER|QUARTER|RADIANS\"\n    functions=\"${functions}|RAND|REPEAT|REPLACE|REVERSE|RIGHT|ROUND|RPAD|RTRIM|SECOND|SEC_TO_TIME|SESSION_USER\"\n    functions=\"${functions}|SIGN|SIN|SPACE|SQRT|STRCMP|STR_TO_DATE|SUBDATE|SUBSTR|SUBSTRING|SUBSTRING_INDEX\"\n    functions=\"${functions}|SUBTIME|SUM|SYSDATE|SYSTEM_USER|TAN|TIME|TIMEDIFF|TIME_FORMAT|TIMESTAMP\"\n    functions=\"${functions}|TIME_TO_SEC|TO_DAYS|TRIM|TRUNCATE|UCASE|UPPER|USER|VERSION|WEEK|WEEKDAY|WEEKOFYEAR\"\n    functions=\"${functions}|YEAR|YEARWEEK\"\n\n    # SQL Server functions\n    functions=\"${functions}|CHAR|CHARINDEX|DATALENGTH|DATEADD|DATENAME|DATEPART|GETDATE|GETUTCDATE|ISDATE\"\n    functions=\"${functions}|ISNUMERIC|LEN|NCHAR|PATINDEX|SESSIONPROPERTY|STR|STUFF|USER_NAME\"\n\n    # MS Access functions\n    functions=\"${functions}|Abs|Asc|Atn|Avg|Chr|Cos|Count|CurDir|CurrentUser|Date|DateAdd|DateDiff|DatePart\"\n    functions=\"${functions}|DateSerial|DateValue|Day|Environ|Exp|Fix|Format|Hour|InStr|InstrRev|Int|IsDate\"\n    functions=\"${functions}|IsNull|IsNumeric|LCase|Left|Len|LTrim|Max|Mid|Min|Minute|Month|MonthName|Now\"\n    functions=\"${functions}|Randomize|Replace|Right|Rnd|Round|RTrim|Second|Sgn|Space|Split|Sqr|Str|StrComp\"\n    functions=\"${functions}|StrConv|StrReverse|Sum|Time|TimeSerial|TimeValue|Trim|UCase|Val|Weekday\"\n    functions=\"${functions}|WeekdayName|Year\"\n\n    # Oracle functions\n    functions=\"${functions}|ADD_MONTHS|ASCIISTR|BITAND|CHR|COMPOSE|COSH|DBTIMEZONE|DECOMPOSE|DUMP|INITCAP|INSTRB\"\n    functions=\"${functions}|INSTRC|LENGTHB|LENGTHC|MEDIAN|MONTHS_BETWEEN|NCHR|NEW_TIME|NEXT_DAY|REGEXP_COUNT\"\n    functions=\"${functions}|REGEXP_INSTR|REGEXP_REPLACE|REGEXP_SUBSTR|REMAINDER|ROWNUM|SESSIONTIMEZONE|SOUNDEX\"\n    functions=\"${functions}|SYSTIMESTAMP|TANH|TRANSLATE|TRUNC|TZ_OFFSET|VSIZE\"\n\n    # MySQL data types\n    data_types=\"LONGBLOB|LONGTEXT|MEDIUMBLOB|MEDIUMTEXT|SET|TEXT|TINYTEXT\"\n    data_types_fn=\"BIGINT|BLOB|CHAR|DATE|DATETIME|DECIMAL|DOUBLE|ENUM|FLOAT|INT\"\n    data_types_fn=\"${data_types_fn}|MEDIUMINT|SMALLINT|TIME|TIMESTAMP|TINYINT|VARCHAR|YEAR\"\n\n    # SQL Server data types\n    data_types=\"${data_types}|bigint|bit|cursor|date|datetime|datetime2|datetimeoffset|image|int|money|nchar|ntext\"\n    data_types=\"${data_types}|nvarchar|real|smalldatetime|smallint|smallmoney|sql_variant|table|text|time\"\n    data_types=\"${data_types}|timestamp|tinyint|uniqueidentifier|varbinary|xml\"\n    data_types_fn=\"${data_types_fn}|binary|char|decimal|float|numeric|nvarchar|varbinary|varchar|varchar\"\n\n    # MS Access data types\n    data_types=\"${data_types}|Text|Memo|Byte|Integer|Long|Single|Double|Currency|AutoNumber|Date\"\n    data_types=\"${data_types}|Time|Ole Object|Hyperlink|Lookup Wizard\"\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list sql_static_words ${keywords} ${operators} ${functions} ${data_types} ${data_types_fn} NULL\" | tr '|' ' '\n\n    # Highlight keywords\n    printf %s \"\n        add-highlighter shared/sql/code/ regex '(?i)\\b(${functions})\\(.*?\\)' 0:function\n        add-highlighter shared/sql/code/ regex '(?i)\\b(${data_types_fn})\\(.*?\\)' 0:type\n        add-highlighter shared/sql/code/ regex '(?i)\\b(${keywords})\\b' 0:keyword\n        add-highlighter shared/sql/code/ regex '(?i)\\b(${operators})\\b' 0:operator\n        add-highlighter shared/sql/code/ regex '(?i)\\b(${data_types})\\b' 0:type\n    \"\n}\n\nadd-highlighter shared/sql/code/ regex '\\+|-|\\*|/|%|&|\\||^|=|>|<|>=|<=|<>|\\+=|-=|\\*=|/=|%=|&=|^-=|\\|\\*=' 0:operator\nadd-highlighter shared/sql/code/ regex \\bNULL\\b 0:value\nadd-highlighter shared/sql/code/ regex \\b\\d+(?:\\.\\d+)?\\b 0:value\n\n}\n"
  },
  {
    "path": "rc/filetype/sshconfig.kak",
    "content": "hook global BufCreate .*/\\.ssh/config %{\n    set-option buffer filetype ssh\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=ssh %{\n    require-module ssh\n\n    set-option window static_words %opt{ssh_static_words}\n\n    hook window InsertChar \\n -group ssh-insert ssh-insert-on-new-line\n    hook window InsertChar \\n -group ssh-indent ssh-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window ssh-.+ }\n}\n\nhook -group ssh-highlight global WinSetOption filetype=ssh %{\n    add-highlighter window/ssh ref ssh\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/ssh }\n}\n\nprovide-module ssh %@\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/ssh regions\nadd-highlighter shared/ssh/code default-region group\n\nadd-highlighter shared/ssh/double_string region %{(?<!\\\\)(?:\\\\\\\\)*\\K\"} %{(?<!\\\\)(?:\\\\\\\\)*\"} group\nadd-highlighter shared/ssh/single_string region %{(?<!\\\\)(?:\\\\\\\\)*\\K'} %{'} fill string\n\nadd-highlighter shared/ssh/comment region (?<!\\\\)(?:\\\\\\\\)*(?:^|\\h)\\K# '$' fill comment\n\nadd-highlighter shared/ssh/double_string/fill fill string\n\nevaluate-commands %sh{\n    keywords=\"AddKeysToAgent AddressFamily AskPassGUI BatchMode BindAddress CanonicalDomains CanonicalizeFallbackLocal CanonicalizeHostname\n              CanonicalizeMaxDots CanonicalizePermittedCNAMEs ChallengeResponseAuthentication CheckHostIP Cipher Ciphers ClearAllForwardings\n              Compression CompressionLevel ConnectionAttempts ConnectTimeout ControlMaster ControlPath ControlPersist DynamicForward\n              EnableSSHKeysign EscapeChar ExitOnForwardFailure FingerprintHash ForwardAgent ForwardX11 ForwardX11Timeout ForwardX11Trusted\n              GatewayPorts GlobalKnownHostsFile GSSAPIAuthentication GSSAPIClientIdentity GSSAPIDelegateCredentials GSSAPIKeyExchange\n              GSSAPIRenewalForcesRekey GSSAPIServerIdentity GSSAPITrustDns HashKnownHosts Host HostbasedAuthentication HostbasedKeyTypes\n              HostKeyAlgorithms HostKeyAlias HostName IdentitiesOnly IdentityFile IgnoreUnknown IPQoS KbdInteractiveAuthentication\n              KbdInteractiveDevices KexAlgorithms KeychainIntegration LocalCommand LocalForward LogLevel MACs Match NoHostAuthenticationForLocalhost\n              NumberOfPasswordPrompts PasswordAuthentication PermitLocalCommand PKCS11Provider Port PreferredAuthentications Protocol ProxyCommand\n              ProxyJump ProxyUseFdpass PubkeyAuthentication RekeyLimit RemoteForward RequestTTY RevokedHostKeys RhostsRSAAuthentication\n              RSAAuthentication SendEnv ServerAliveCountMax ServerAliveInterval SmartcardDevice StreamLocalBindMask StreamLocalBindUnlink\n              StrictHostKeyChecking TCPKeepAlive Tunnel TunnelDevice UpdateHostKeys UseKeychain UsePrivilegedPort User UserKnownHostsFile\n              VerifyHostKeyDNS VisualHostKey XAuthLocation\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    printf %s\\\\n \"declare-option str-list ssh_static_words $(join \"${keywords}\" ' ') $(join \"${builtins}\" ' ')\"\n\n    printf %s\\\\n \"add-highlighter shared/ssh/code/ regex (?<!-)\\b($(join \"${keywords}\" '|'))\\b(?!-) 0:keyword\"\n}\n\nadd-highlighter shared/ssh/code/ regex ^host\\s+ 0:keyword\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden ssh-insert-on-new-line %{ evaluate-commands -itersel -draft %{\n    execute-keys <semicolon>\n    try %{\n        evaluate-commands -draft -save-regs '/\"' %{\n            # Ensure previous line is a comment\n            execute-keys -draft kxs^\\h*#+\\h*<ret>\n\n            # now handle the coment continuation logic\n            try %{\n                # try and match a regular block comment, copying the prefix\n                execute-keys -draft -save-regs '' k x 1s^(\\h*#+\\h*)\\S.*$ <ret> y\n                execute-keys -draft P\n            } catch %{\n                try %{\n                    # try and match a regular block comment followed by a single\n                    # empty comment line\n                    execute-keys -draft -save-regs '' kKx 1s^(\\h*#+\\h*)\\S+\\n\\h*#+\\h*$ <ret> y\n                    execute-keys -draft P\n                } catch %{\n                    try %{\n                        # try and match a pair of empty comment lines, and delete\n                        # them if we match\n                        execute-keys -draft kKx <a-k> ^\\h*#+\\h*\\n\\h*#+\\h*$ <ret> <a-d>\n                    } catch %{\n                        # finally, we need a special case for a new line inserted\n                        # into a file that consists of a single empty comment - in\n                        # that case we can't expect to copy the trailing whitespace,\n                        # so we add our own\n                        execute-keys -draft -save-regs '' k x1s^(\\h*#+)\\h*$<ret> y\n                        execute-keys -draft P\n                        execute-keys -draft i<space>\n                    }\n                }\n            }\n        }\n\n        # trim trailing whitespace on the previous line\n        try %{ execute-keys -draft k x s\\h+$<ret> d }\n    }\n} }\n\ndefine-command -hidden ssh-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n    >\n>\n\n@\n"
  },
  {
    "path": "rc/filetype/svelte.kak",
    "content": "hook global BufCreate .*\\.svelte %[\n    set-option buffer filetype svelte\n]\n\nhook global WinSetOption filetype=(svelte) %{\n    require-module html\n\n    hook window ModeChange pop:insert:.* -group \"svelte-trim-indent\"  html-trim-indent\n    hook window InsertChar '>' -group \"svelte-indent\" html-indent-on-greater-than\n    hook window InsertChar \\n -group \"svelte-indent\" html-indent-on-new-line\n\n    hook -once -always window WinSetOption \"filetype=.*\" \"\n        remove-hooks window \"\"svelte-.+\"\"\n    \"\n}\n\nhook -group svelte-highlight global WinSetOption filetype=(svelte) %{\n    add-highlighter \"window/svelte\" ref svelte\n    hook -once -always window WinSetOption \"filetype=.*\" \"\n        remove-highlighter \"\"window/svelte\"\"\n    \"\n}\n\nadd-highlighter shared/svelte regions\nadd-highlighter shared/svelte/comment region <!--     -->                  fill comment\nadd-highlighter shared/svelte/tag     region <          >                  regions\nadd-highlighter shared/svelte/style   region <style\\b.*?>\\K  (?=</style>)  ref css\nadd-highlighter shared/svelte/script  region <script\\b.*?>\\K (?=</script>) ref javascript\n\nadd-highlighter shared/svelte/block region \\{((#|:|/)\\w+)? \\} regions\nadd-highlighter shared/svelte/block/ default-region fill meta\nadd-highlighter shared/svelte/block/inner region -recurse \\{ \\{((#|:|/)\\w+)?\\K (?=\\}) ref javascript\n\nadd-highlighter shared/svelte/tag/base default-region ref html/tag\nadd-highlighter shared/svelte/tag/block region -recurse \\{ \\{ \\} ref svelte/block\n"
  },
  {
    "path": "rc/filetype/swift.kak",
    "content": "#\n# MARK: - detection\n#\n\nhook global BufCreate .*\\.(swift) %{\n    set-option buffer filetype swift\n}\n\n#\n# MARK: - initialisation\n#\n\nhook global WinSetOption filetype=swift %<\n    require-module swift\n    hook window ModeChange pop:insert:.* -group swift-trim-indent swift-trim-indent\n\n    hook window InsertChar \\n -group swift-insert swift-insert-on-new-line\n    hook window InsertChar \\n -group swift-indent swift-indent-on-new-line\n    hook window InsertChar \\} -group swift-indent swift-indent-on-closing\n\n    hook -once -always window WinSetOption filetype=.* %< remove-hooks window swift-.+ >\n>\n\nhook -group swift-highlight global WinSetOption filetype=swift %{\n    add-highlighter window/swift ref swift\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/swift }\n}\n\nprovide-module swift %§\n\n#\n# MARK: - highlighters\n#\n\nadd-highlighter shared/swift regions\nadd-highlighter shared/swift/code default-region group\n\nadd-highlighter shared/swift/comment region /\\* \\*/ group\nadd-highlighter shared/swift/line_comment region // $ ref swift/comment\nadd-highlighter shared/swift/comment/ fill comment\n\nadd-highlighter shared/swift/string_multiline region %{(?<!')\"\"\"} %{(?<!\\\\)(\\\\\\\\)*\"\"\"} regions\nadd-highlighter shared/swift/string_multiline/base default-region fill string\nadd-highlighter shared/swift/string_multiline/interpolation region -recurse \\( \\Q\\( \\) fill meta\n\nadd-highlighter shared/swift/string region %{(?<!')\"} %{(?<!\\\\)(\\\\\\\\)*\"} regions\nadd-highlighter shared/swift/string/base default-region fill string\nadd-highlighter shared/swift/string/interpolation region -recurse \\( \\Q\\( \\) fill meta\n\n# backticked identifiers\nadd-highlighter shared/swift/backtick_identifier region ` ` fill meta\n\n# discard literal\nadd-highlighter shared/swift/code/ regex \"\\b_\\b\" 0:comment\n\n# values\nadd-highlighter shared/swift/code/ regex \\b(?:true|false|nil)\\b 0:value\nadd-highlighter shared/swift/code/ regex \\b(?:self|super)\\b 0:value\n\n# Numeric literals with underscores, hex, binary, octal, floats (plus/minus are not painted, matching Xcode)\n#   - Decimal with underscores: 1_000_000, 3.141_592_653_59\n#   - Hexadecimal: 0xFF, 0x1A_2B, 0xDEAD_BEEF\n#   - Hex floats: 0xF.8p2, +0x1_A.F_Fp3\n#   - Binary: 0b1010, 0b1111_0000\n#   - Octal: 0o17, +0o7_7_7, 0o7_7_7\n#   - Decimal floats: 6.28, 1.5e10, +1.5e-5, 1.5E+10\n#   - Negative numbers: -42, -3.14\n#\n# Pattern breakdown:\n#   [-+]?  # optional plus or minus\n#   (?:\n#     0x[0-9a-fA-F][_0-9a-fA-F]*            # hex integer/float start\n#        (?:\\.[0-9a-fA-F][_0-9a-fA-F]*)?    # optional hex fraction\n#        (?:[pP][+-]?[0-9][_0-9]*)?         # optional binary exponent\n#     |0o[0-7][_0-7]*                       # octal\n#     |0b[01][_01]*                         # binary\n#     |[0-9][_0-9]*                         # decimal integer/float start\n#        (?:\\.[0-9][_0-9]*)?                # optional decimal fraction\n#        (?:[eE][+-]?[0-9][_0-9]*)?         # optional decimal exponent\n#   )\n#\nadd-highlighter shared/swift/code/ regex \\b[+-]?(?:0x[0-9a-fA-F][_0-9a-fA-F]*(?:\\.[0-9a-fA-F][_0-9a-fA-F]*)?(?:[pP][+-]?[0-9][_0-9]*)?|0o[0-7][_0-7]*|0b[01][_01]*|[0-9][_0-9]*(?:\\.[0-9][_0-9]*)?(?:[eE][+-]?[0-9][_0-9]*)?)\\b 0:value\n\n# keywords\nadd-highlighter shared/swift/code/ regex \"\\b(let|var|while|in|for|if|guard|else|do|switch|case|default|break|continue|return|try|catch|throw|operator|func|import|init|deinit|get|set|defer|repeat|fallthrough|async|await|throws|rethrows|inout|where|is|subscript|macro|protocol|typealias|actor|class|struct|enum|extension|some|any|associatedtype|distributed|isolated|nonisolated|consuming|borrowing|borrow|move|discard|sending|nonsending)\\b\" 0:keyword\nadd-highlighter shared/swift/code/ regex \"\\bas\\b[!?]?\" 0:keyword\nadd-highlighter shared/swift/code/ regex \"(\\$[0-9])\\b\" 0:keyword\n\n# types\nadd-highlighter shared/swift/code/ regex \"[\\[]?\\b(Bool|String|Character|Int|Int8|Int16|Int32|Int64|Int128|UInt|UInt8|UInt16|UInt32|UInt64|UInt128|Float|Float16|Float32|Float64|Float80|Double|Void|Never|Any|AnyObject|AnyClass|Optional|Array|Dictionary|Set|Range|ClosedRange|PartialRangeFrom|PartialRangeThrough|PartialRangeUpTo|Result|Error|Equatable|Hashable|Comparable|Codable|Encodable|Decodable|Sendable|CaseIterable|CodingKey|Task|TaskGroup|AsyncStream|AsyncThrowingStream|GlobalActor|UnsafePointer|UnsafeMutablePointer|UnsafeRawPointer|UnsafeMutableRawPointer|UnsafeBufferPointer|UnsafeMutableBufferPointer|UnsafeRawBufferPointer|UnsafeMutableRawBufferPointer|Unmanaged|AutoreleasingUnsafeMutablePointer|SIMD|SIMD2|SIMD3|SIMD4|SIMD8|SIMD16|SIMD32|SIMD64|SIMDMask|SIMDScalar|SIMDStorage)\\b[\\]]?[!?]?\" 0:type\n\n# compilation directives\nadd-highlighter shared/swift/code/ regex \"#(if|elseif|else|endif|available|unavailable|warning|error|sourceLocation|file|fileID|filePath|line|column|function|dsohandle|selector|keyPath|colorLiteral|imageLiteral|fileLiteral)\\b\" 0:meta\nadd-highlighter shared/swift/code/ regex \"\\b(canImport|os|arch|swift|compiler|targetEnvironment)[\\s\\(]+\\b\" 1:meta\n\n# attributes\nadd-highlighter shared/swift/code/ regex \"\\b(inline|static|open|public|private|fileprivate|internal|package|override|final|required|convenience|dynamic|lazy|mutating|nonmutating|indirect|weak|unowned|didSet|willSet)\\b\" 0:attribute\nadd-highlighter shared/swift/code/ regex \"\\b(IBAction|IBOutlet)\\b\" 0:attribute\nadd-highlighter shared/swift/code/ regex \"@\\w+\\b\" 0:attribute\n\n#\n# MARK: commands\n#\n\ndefine-command -hidden swift-trim-indent %{\n    # delete trailing whitespace\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden swift-insert-on-new-line %<\n    try %[\n        evaluate-commands -draft -save-regs '/\"\\' %[\n            # copy // comments prefix and following whitespace\n            execute-keys -save-regs '' k x1s^\\h*(//+\\h*)<ret> y\n            try %[\n                # if the previous comment isn't empty, create a new one\n                execute-keys x<a-K>^\\h*//+\\h*$<ret> jxs^\\h*<ret>P\n            ] catch %[\n                # if there is no text in the previous comment, remove it completely\n                execute-keys d\n            ]\n        ]\n\n        # trim trailing whitespace on the previous line\n        try %[ execute-keys -draft k x s\\h+$<ret> d ]\n    ]\n>\n\ndefine-command -hidden swift-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve indent level\n        try %< execute-keys -draft <semicolon> K <a-&> >\n        try %<\n            # only if we didn't copy a comment\n            execute-keys -draft x <a-K> ^\\h*// <ret>\n            # indent after lines ending in {\n            try %< execute-keys -draft k x <a-k> \\{\\h*$ <ret> j <a-gt> >\n            # indent after lines ending in 'in' (closure parameters)\n            try %< execute-keys -draft k x <a-k> \\bin\\h*$ <ret> j <a-gt> >\n            # deindent closing } when after cursor\n            try %< execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> >\n        >\n        # filter previous line\n        try %< execute-keys -draft k : swift-trim-indent <ret> >\n    >\n>\n\ndefine-command -hidden swift-indent-on-closing %<\n    # align lone } to indent level of opening line\n    try %< execute-keys -draft -itersel <a-h> <a-k> ^\\h*\\}$ <ret> h m <a-S> 1<a-&> >\n>\n\n§\n"
  },
  {
    "path": "rc/filetype/systemd.kak",
    "content": "# https://freedesktop.org/wiki/Software/systemd/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*/systemd/.+\\.(automount|conf|link|mount|network|path|service|slice|socket|target|timer) %{\n    set-option buffer filetype ini\n\n    # NOTE: INI files define the commenting character to be `;`, which won't work in `systemd` files\n    hook -once buffer BufSetOption comment_line=.+ %{\n        set-option buffer comment_line \"#\"\n    }\n}\n"
  },
  {
    "path": "rc/filetype/taskpaper.kak",
    "content": "# https://www.taskpaper.com\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.taskpaper %{\n    set-option buffer filetype taskpaper\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=taskpaper %{\n    require-module taskpaper\n\n    hook window ModeChange pop:insert:.* -group taskpaper-trim-indent taskpaper-trim-indent\n    hook window InsertChar \\n -group taskpaper-indent taskpaper-indent-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window taskpaper-.+ }\n}\n\nhook -group taskpaper-highlight global WinSetOption filetype=taskpaper %{\n    add-highlighter window/taskpaper ref taskpaper\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/taskpaper }\n}\n\n\nprovide-module taskpaper %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/taskpaper group\n\nadd-highlighter shared/taskpaper/ regex ^\\h*([^:\\n]+):\\h*\\n 1:header\nadd-highlighter shared/taskpaper/ regex \\h@\\w+(?:\\(([^)]*)\\))? 0:variable 1:value\nadd-highlighter shared/taskpaper/ regex ^\\h*([^-:\\n]+)\\n 1:+i\nadd-highlighter shared/taskpaper/ regex ^\\h*-\\h+[^\\n]*@done[^\\n]* 0:+d\nadd-highlighter shared/taskpaper/ regex \\b(([a-z]+://\\S+)|((mailto:)[\\w+-]+@\\S+)) 0:link\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden taskpaper-trim-indent %{\n    evaluate-commands -no-hooks -draft -itersel %{\n        execute-keys x\n        # remove trailing white spaces\n        try %{ execute-keys -draft s \\h + $ <ret> d }\n    }\n}\n\ndefine-command -hidden taskpaper-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        ## If the line above is a project indent with a tab\n        try %{ execute-keys -draft Z kx <a-k>^\\h*([^:\\n]+):<ret> z i<tab> }\n        # cleanup trailing white spaces on previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/tcl.kak",
    "content": "hook global BufCreate .*[.](tcl) %{\n    set-option buffer filetype tcl\n}\n\nhook global WinSetOption filetype=tcl %{\n    require-module tcl\n\n    hook window ModeChange pop:insert:.* -group tcl-trim-indent tcl-trim-indent\n    hook window InsertChar \\n -group tcl-insert tcl-insert-on-new-line\n    hook window InsertChar \\n -group tcl-indent tcl-indent-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window tcl-.+ }\n}\n\nhook -group tcl-highlight global WinSetOption filetype=tcl %{\n    add-highlighter window/tcl ref tcl\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/tcl }\n}\n\n# Using non-ascii characters here so that we can use the '[' command\nprovide-module tcl %§\n\nadd-highlighter shared/tcl regions\nadd-highlighter shared/tcl/code default-region group\nadd-highlighter shared/tcl/comment region (?<!\\\\)(?:\\\\\\\\)*(?:^|\\h)\\K# '$' fill comment\nadd-highlighter shared/tcl/double_string region  %{(?<!\\\\)(?:\\\\\\\\)*\\K\"} %{(?<!\\\\)(?:\\\\\\\\)*\"} group\nadd-highlighter shared/tcl/double_string/fill fill string\n\nevaluate-commands %sh{\n    # Tcl does not have keywords, as everything in Tcl is a command.\n    # Highlight all builtin commads does not make a lot of sense as there are plenty of them.\n    # What is more, sometimes user defines varaibles have the same names as commands.\n    # On the other hand, highlight no commads makes the code harder to read.\n    # The approach for builtin commads highlighting is very simply.\n    # Highlight only two types of commands:\n    #   1. \"Control statement\" like commands, for exmaple, \"if\", \"break\", \"return\", etc.\n    #   2. Commands defining new scope, for exmaple, \"proc\", \"namespace\".\n    keywords=\"break catch continue default else elseif error exit for foreach if return switch while proc namespace\"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Highlight keywords\n    printf %s\\\\n \"add-highlighter shared/tcl/code/ regex (?<!-)\\b($(join \"${keywords}\" '|'))\\b(?!-) 0:keyword\"\n}\n\nadd-highlighter shared/tcl/code/function   regex ^\\h*proc\\h+((\\w|-)+) 1:function\nadd-highlighter shared/tcl/code/brackets   regex [\\[\\]]{1,2}          0:operator\nadd-highlighter shared/tcl/code/parameters regex \\s-\\w+\\b             0:attribute\nadd-highlighter shared/tcl/code/variable   regex \\$(\\w|:)+            0:variable\nadd-highlighter shared/tcl/code/numbers    regex '\\b\\d+\\.?'           0:value\n\nadd-highlighter shared/tcl/double_string/variable regex \\$(\\w|:)+   0:variable\nadd-highlighter shared/tcl/double_string/brackets regex [\\[\\]]{1,2} 0:operator\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden tcl-trim-indent %{\n    # Remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden tcl-insert-on-new-line %[\n    # Copy '#' comment prefix and following white spaces\n    try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n]\n\ndefine-command -hidden tcl-indent-on-new-line %¶\n    evaluate-commands -draft -itersel %@\n        # Preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n\n        # Filter previous line\n        try %{ execute-keys -draft k : tcl-trim-indent <ret> }\n\n        # Indent after {\n        try %= execute-keys -draft , k x <a-k> (\\s|^)\\{$ <ret> j <a-gt> =\n    @\n¶\n§\n"
  },
  {
    "path": "rc/filetype/terraform.kak",
    "content": "# Terraform configuration language\n# https://www.terraform.io/docs/configuration/\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](tf|tfvars) %{\n  set-option buffer filetype terraform\n}\n\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=terraform %{\n    require-module terraform\n\n    set-option window static_words %opt{terraform_static_words}\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window terraform-.+ }\n}\n\n\nhook -group terraform-highlight global WinSetOption filetype=terraform %{\n    add-highlighter window/terraform ref terraform\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/terraform }\n}\n\n\nprovide-module terraform %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/terraform regions\nadd-highlighter shared/terraform/code  default-region group\n\nadd-highlighter shared/terraform/comment1 region '#'    '$'  fill comment\nadd-highlighter shared/terraform/comment2 region '\\\\'   '$'  fill comment\nadd-highlighter shared/terraform/comment3 region /\\*    \\*/  fill comment\n\n# Strings can contain interpolated terraform expressions, which can contain\n# strings. Currently, we cannot support nesting of the same type of delimiter,\n# so instead we render the full interpolation as a value (otherwise, it\n# looks bad).\n# See https://github.com/mawww/kakoune/issues/1670\nadd-highlighter shared/terraform/string  region '\"' '(?<!\\\\)(?:\\\\\\\\)*\"'  group\nadd-highlighter shared/terraform/string/fill fill string\nadd-highlighter shared/terraform/string/inter regex \\$\\{.+?\\} 0:value\n\nadd-highlighter shared/terraform/heredoc region -match-capture '<<-?(\\w+)' '^\\h*(\\w+)$' regions\nadd-highlighter shared/terraform/heredoc/fill default-region fill string\nadd-highlighter shared/terraform/heredoc/inter region -recurse \\{ (?<!\\\\)(\\\\\\\\)*\\$\\{ \\} ref terraform\n\n\nadd-highlighter shared/terraform/code/valueDec regex '\\b[0-9]+([kKmMgG]b?)?\\b' 0:value\nadd-highlighter shared/terraform/code/valueHex regex '\\b0x[0-9a-f]+([kKmMgG]b?)?\\b' 0:value\n\nadd-highlighter shared/terraform/code/operators regex [\\[\\]] 0:operator\n\nadd-highlighter shared/terraform/code/field regex '^\\h+(\\w+)\\s*(=)' 1:variable 2:keyword\n\nevaluate-commands %sh{\n  blocks=\"connection content data dynamic locals module output provider\n          provisioner resource terraform variable\"\n\n  constants=\"true false null\"\n\n  keywords=\"for for_each if in\"\n\n  types=\"bool list map number object set string tuple\"\n\n  var_subs=\"local module var\"\n\n  # Builtin functions\n  fun_num=\"abs ceil floor log max min parseint pow signum\"\n\n  fun_str=\"chomp format formatlist indent join lower regex regexall replace\n           split strrev substr title trimspace upper\"\n\n  fun_coll=\"chunklist coalesce coalescelist compact concat contains\n            distinct element flatten index keys length lookup\n            matchkeys merge range reverse setintersection setproduct\n            setunion slice sort transpose values zipmap\"\n\n  fun_enc=\"base64decode base64encode base64gzip csvdecode jsondecode\n           jsonencode urlencode yamldecode yamlencode\"\n\n  fun_file=\"abspath dirname pathexpand basename file fileexists fileset\n            filebase64 templatefile\"\n\n  fun_dt=\"formatdate timeadd timestamp\"\n\n  fun_crypt=\"base64sha256 base64sha512 bcrypt filebase64sha256\n             filebase64sha512 filemd5 filesha1 filesha256 filesha512 md5\n             rsadecrypt sha1 sha256 sha512 uuid uuidv5\"\n\n  fun_net=\"cidrhost cidrnetmask cidrsubnet\"\n\n  fun_cast=\"tobool tolist tomap tonumber toset tostring\"\n\n  functions=\"$fun_num $fun_str $fun_coll $fun_enc $fun_file $fun_dt $fun_crypt $fun_net $fun_cast\"\n\n  join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n  # Add grammar elements to the static completion list\n  printf %s\\\\n \"declare-option str-list terraform_static_words $(join \"$blocks $keywords $constants $types $var_subs $functions\" ' ')\"\n\n  # Highlight grammar elements\n  printf %s \"\n    add-highlighter shared/terraform/code/ regex '\\b($(join \"$blocks\"    '|'))\\b[^.]' 1:keyword\n    add-highlighter shared/terraform/code/ regex '\\b($(join \"$keywords\"  '|'))\\b'     1:keyword\n    add-highlighter shared/terraform/code/ regex '\\b($(join \"$constants\" '|'))\\b'     1:value\n    add-highlighter shared/terraform/code/ regex '\\b($(join \"$types\"     '|'))\\b'     1:type\n    add-highlighter shared/terraform/code/ regex '\\b($(join \"$var_subs\"  '|'))\\b\\.'   1:meta\n    add-highlighter shared/terraform/code/ regex '\\b($(join \"$functions\" '|'))\\s*\\('  1:builtin\n  \"\n}\n\n§\n"
  },
  {
    "path": "rc/filetype/toml.kak",
    "content": "# https://github.com/toml-lang/toml/tree/v0.4.0\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.(toml) %{\n    set-option buffer filetype toml\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=toml %{\n    require-module toml\n\n    hook window ModeChange pop:insert:.* -group toml-trim-indent toml-trim-indent\n    hook window InsertChar \\n -group toml-insert toml-insert-on-new-line\n    hook window InsertChar \\n -group toml-indent toml-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window toml-.+ }\n}\n\nhook -group toml-highlight global WinSetOption filetype=toml %{\n    add-highlighter window/toml ref toml\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/toml }\n}\n\n\nprovide-module toml %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/toml regions\nadd-highlighter shared/toml/code default-region group\nadd-highlighter shared/toml/comment region '#'   $           fill comment\nadd-highlighter shared/toml/string1 region  '\"\"\"' (?<!\\\\)(\\\\\\\\)*\"\"\"(?!\") fill string\nadd-highlighter shared/toml/string2 region  \"'''\" \"'''(?!')\"             fill string\nadd-highlighter shared/toml/string3 region  '\"'   (?<!\\\\)(\\\\\\\\)*\"        fill string\nadd-highlighter shared/toml/string4 region  \"'\"   \"'\"                    fill string\n\nadd-highlighter shared/toml/code/ regex \\\n    \"^\\h*\\[\\[?([A-Za-z0-9._-]*)\\]\\]?\" 1:title\nadd-highlighter shared/toml/code/ regex \\\n    (?<!\\w)[+-]?[0-9](_?\\d)*(\\.[0-9](_?\\d)*)?([eE][+-]?[0-9](_?\\d)*)?\\b 0:value\nadd-highlighter shared/toml/code/ regex \\\n    true|false 0:value\nadd-highlighter shared/toml/code/ regex \\\n    '\\d{4}-\\d{2}-\\d{2}[Tt ]\\d{2}:\\d{2}:\\d{2}(.\\d+)?([Zz]|[+-]\\d{2}:\\d{2})' 0:value\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden toml-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden toml-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy # comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden toml-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : toml-trim-indent <ret> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/troff.kak",
    "content": "# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.\\d+ %{\n    set-option buffer filetype troff\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=troff %{\n    require-module troff\n}\n\nhook -group troff-highlight global WinSetOption filetype=troff %{\n    add-highlighter window/troff ref troff\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/troff }\n}\n\nprovide-module troff %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/troff regions\n\nadd-highlighter shared/troff/text default-region group\nadd-highlighter shared/troff/text/ regex '(^\\.)?\\\\\".*?\\n' 0:comment\nadd-highlighter shared/troff/text/ regex '\\\\f[A-Z]' 0:attribute\nadd-highlighter shared/troff/text/ regex '\\\\fB(.+?)\\\\f[A-Z]' 1:+b\nadd-highlighter shared/troff/text/ regex '\\\\fI(.+?)\\\\f[A-Z]' 1:+i\nadd-highlighter shared/troff/text/ regex '^\\.[a-zA-Z]{1,2}\\b' 0:meta\nadd-highlighter shared/troff/text/ regex '^\\.(PSPIC|PDFPIC|pdfhref)\\b' 0:meta\nadd-highlighter shared/troff/text/ regex '^\\.\\.$' 0:meta\nadd-highlighter shared/troff/text/ regex '^\\.TH\\s+[^\\n]+' 0:title\nadd-highlighter shared/troff/text/ regex '^\\.NH(\\s+\\d+(\\s+\\d+)?)?\\s*\\n' 0:header\nadd-highlighter shared/troff/text/ regex '^\\.SH(\\s+\\d+)?\\s*\\n' 0:header\nadd-highlighter shared/troff/text/ regex '^\\.IR\\s+(\\S+)' 1:+i\nadd-highlighter shared/troff/text/ regex '^\\.BR\\s+(\\S+)' 1:+b\nadd-highlighter shared/troff/text/ regex '^\\.I\\s+([^\\n]+)' 1:+i\nadd-highlighter shared/troff/text/ regex '^\\.B\\s+([^\\n]+)' 1:+b\nadd-highlighter shared/troff/text/ regex '(ftp:|http:|https:|www\\.)+[^\\s]+[\\w]' 0:link\n\nadd-highlighter shared/troff/pic region '^\\.PS\\b' '^\\.PE\\b' group\nadd-highlighter shared/troff/pic/ regex '^(\\.PS\\b|\\.PE\\b)' 1:meta\nadd-highlighter shared/troff/pic/ regex '^(copy)\\s+' 1:keyword\nadd-highlighter shared/troff/pic/ regex '\\b(arc|arrow|box|circle|ellipse|line|move|spline)\\b' 1:type\nadd-highlighter shared/troff/pic/ regex '\\b(above|at|below|by|center|chop|dashed|diam|diameter|down|dotted|fill|from|ht|height|invis|left|ljust|rad|radius|right|rjust|solid|then|to|up|wid|width|with)\\b' 1:attribute\nadd-highlighter shared/troff/pic/ regex '(\\s+|\\+|-|\\*|/)(\\d+(\\.\\d+)?)' 2:value\nadd-highlighter shared/troff/pic/ regex '\"[^\"]*\"' 0:string\n}\n"
  },
  {
    "path": "rc/filetype/ttl.kak",
    "content": "# Terse RDF Triple Language (Turtle)\n# of the W3C's Resource Description Framework (RDF):\n# https://www.w3.org/TR/turtle/\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*\\.(ttl) %{\n    set-option buffer filetype ttl\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=ttl %{\n    require-module ttl\n\n    hook window ModeChange pop:insert:.* -group ttl-trim-indent ttl-trim-indent\n    hook window InsertChar \\n -group ttl-insert ttl-insert-on-new-line\n    hook window InsertChar \\n -group ttl-indent ttl-indent-on-new-line\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window ttl-.+ }\n}\n\nhook -group ttl-highlight global WinSetOption filetype=ttl %{\n    add-highlighter window/ttl ref ttl\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/ttl }\n}\n\n\nprovide-module ttl %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/ttl regions\nadd-highlighter shared/ttl/code default-region group\n\nadd-highlighter shared/ttl/comment region '#+\\h'   $           fill comment\nadd-highlighter shared/ttl/string1 region  '\"\"\"' (?<!\\\\)(\\\\\\\\)*\"\"\"(?!\") fill string\nadd-highlighter shared/ttl/string2 region  \"'''\" \"'''(?!')\"             fill string\nadd-highlighter shared/ttl/string3 region  '\"'   (?<!\\\\)(\\\\\\\\)*\"        fill string\nadd-highlighter shared/ttl/string4 region  \"'\"   \"'\"                    fill string\n\nadd-highlighter shared/ttl/code/comment2  regex '^\\h*(#+)$'                1:comment\nadd-highlighter shared/ttl/code/colon     regex (:)                        1:operator\nadd-highlighter shared/ttl/code/separator regex ([\\;\\.,<>\\[\\]\\(\\)])        1:delimiter\nadd-highlighter shared/ttl/code/term      regex :(w+)\\b                    1:variable\nadd-highlighter shared/ttl/code/prefix    regex \\b([-.\\w]+):               1:module\nadd-highlighter shared/ttl/code/def       regex (@\\w+)\\b                   1:attribute\nadd-highlighter shared/ttl/code/cdef      regex \\b(BASE|PREFIX)\\b          1:attribute\nadd-highlighter shared/ttl/code/type      regex (\\^\\^)                     1:type\nadd-highlighter shared/ttl/code/bool      regex \\b(true|false)\\b           1:value\nadd-highlighter shared/ttl/code/num       regex \\b([+-]?[0-9]+\\.*[0-9]*)\\b 1:value\nadd-highlighter shared/ttl/code/keyword   regex \\b(_|a)\\b                  1:keyword\n# Last position, to override single characters.\nadd-highlighter shared/ttl/code/IRI       regex <(\\H*)>                    1:identifier\n\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden ttl-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden ttl-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy # comments prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden ttl-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : ttl-trim-indent <ret> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/tupfile.kak",
    "content": "# http://gittup.org/tup/\n#\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*/?Tup(file|rules)(\\.\\w+)?$ %{\n    set-option buffer filetype tupfile\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook -group tupfile-highlight global WinSetOption filetype=tupfile %{\n    require-module tupfile\n\n    add-highlighter window/tupfile ref tupfile\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/tupfile }\n}\n\n\nprovide-module tupfile %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/tupfile regions\nadd-highlighter shared/tupfile/code default-region group\nadd-highlighter shared/tupfile/string region '\"' (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/tupfile/comment region '#' $ fill comment\n\nadd-highlighter shared/tupfile/code/ regex '%[fbBeoOdg]\\b' 0:value\nadd-highlighter shared/tupfile/code/ regex '[$@]\\([\\w_]+\\)' 0:value\nadd-highlighter shared/tupfile/code/ regex '^\\h*:\\s*(foreach)\\b' 1:keyword\nadd-highlighter shared/tupfile/code/ regex '^\\h*(\\.gitignore)\\b' 1:keyword\nadd-highlighter shared/tupfile/code/ regex '^\\h*\\b(ifn?eq|ifn?def|else|endif|error|include|include_rules|run|preload|export)\\b' 0:keyword\nadd-highlighter shared/tupfile/code/ regex '^\\h*\\b(&?[\\w_]+)\\s*[:+]?=' 1:keyword\nadd-highlighter shared/tupfile/code/ regex '`[^`\\n]+`' 0:meta\n\n}\n"
  },
  {
    "path": "rc/filetype/twig.kak",
    "content": "# https://twig.symfony.com/doc/3.x/templates.html\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](twig) %{\n    set-option buffer filetype twig\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=twig %[\n    require-module twig\n\n    hook window ModeChange pop:insert:.* -group twig-trim-indent twig-trim-indent\n    hook window InsertChar \\n -group twig-insert twig-insert-on-new-line\n    hook window InsertChar \\n -group twig-indent twig-indent-on-new-line\n    hook window InsertChar '>' -group twig-indent twig-indent-on-greater-than\n    hook window InsertChar '#' -group twig-auto-close twig-auto-close-delim\n    hook window InsertChar '%' -group twig-auto-close twig-auto-close-delim\n    set-option buffer extra_word_chars '_' '-'\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window twig-.+ }\n]\n\nhook -group twig-highlight global WinSetOption filetype=twig %{\n    add-highlighter window/twig ref twig\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/twig }\n}\n\n\nprovide-module twig %[\n\nrequire-module html\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/twig regions\nadd-highlighter shared/twig/core default-region group\nadd-highlighter shared/twig/comment region \\{# [#]\\} fill comment\nadd-highlighter shared/twig/delim region \\{([%]-?|\\{) (-?[%]|\\})\\} regions\n\nadd-highlighter shared/twig/core/ ref html\n\nadd-highlighter shared/twig/delim/base default-region group\nadd-highlighter shared/twig/delim/double_string region '\"'  (?<!\\\\)(\\\\\\\\)*\" fill string\nadd-highlighter shared/twig/delim/single_string region \"'\"  (?<!\\\\)(\\\\\\\\)*' fill string\nadd-highlighter shared/twig/delim/base/ regex (\\w+)\\h= 1:variable\n\nadd-highlighter shared/twig/delim/base/functions regex \\b(\\w+)\\( 1:function\n\nadd-highlighter shared/twig/delim/base/filters regex \\b(abs|batch|capitalize|column|convert_encoding|country_name|currency_name|currency_symbol|data_uri|date|date_modify|default|e|escape|filter|first|format|format_currency|format_date|format_datetime|format_number|format_time|html_to_markdown|inline_css|inky_to_html|join|json_encode|keys|language_name|last|length|locale_name|lower|map|markdown_to_html|merge|nl2br|number_format|raw|reduce|replace|reverse|round|slice|slug|sort|spaceless|split|striptags|timezone_name|title|trim|u|upper|url_encode)(\\()?\\b 1:operator\n\nadd-highlighter shared/twig/delim/base/tags regex \\b((extends|deprecated|do|flush|import|from|elseif|else|include|set|use)|(end)?(apply|autoescape|block|cache|embed|for|if|macro|sandbox|set|verbatim|with))\\b 0:keyword 0:+i\n\n\nadd-highlighter shared/twig/delim/base/delimiter_code regex (\\{[%]|[%]\\}) 0:function\nadd-highlighter shared/twig/delim/base/delimiter_output regex (\\{\\{|\\}\\}) 0:operator\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden twig-trim-indent                   html-trim-indent\ndefine-command -hidden twig-indent-on-new-line            html-indent-on-new-line\ndefine-command -hidden twig-indent-on-greater-than        html-indent-on-greater-than\n\ndefine-command -hidden twig-auto-close-delim %[\n  evaluate-commands -itersel %[\n    try %[\n      execute-keys -draft <semicolon>hH<a-k>\\h*\\{<ret>lyp\n      execute-keys <esc>hi<space><esc>hi<space>\n    ]\n  ]\n]\n\ndefine-command -hidden twig-insert-on-new-line %[\n  evaluate-commands -draft -itersel %/\n    execute-keys <semicolon>\n    try %[\n      execute-keys -draft kx<a-k>^\\h*\\{\\[%#\\{\\]\\h+$<ret>\n      execute-keys -draft jghd\n    ]\n  /\n]\n\n]\n"
  },
  {
    "path": "rc/filetype/typst.kak",
    "content": "# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](typ) %{\n    set-option buffer filetype typst\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook -group typst-highlight global WinSetOption filetype=typst %{\n    require-module typst\n\n    add-highlighter window/typst ref typst\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/typst }\n    hook window InsertChar \\n -group typst typst-on-new-line\n}\n\nprovide-module typst %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/typst group\n\n# Comments\nadd-highlighter shared/typst/ regex ^//(?:[^\\n/][^\\n]*|)$ 0:comment\n\n# Strings\nadd-highlighter shared/typst/ regex '\"[^\"]*\"' 0:string\n\n# Headings\nadd-highlighter shared/typst/ regex ^=+\\h+[^\\n]+$ 0:header\n\n# Code blocks\n# Raw with optional syntax highlighting\nadd-highlighter shared/typst/ regex '^```[^(```)]*```' 0:mono\n# Multiline monospace\nadd-highlighter shared/typst/ regex '^`[^(`)]*`' 0:mono\n\n# Monospace text\nadd-highlighter shared/typst/ regex \\B(`[^\\n]+?`)\\B 0:mono\nadd-highlighter shared/typst/ regex \\B(```[^\\n]+?```)\\B 0:mono\n\n# Bold text\nadd-highlighter shared/typst/ regex \\s\\*[^\\*]+\\*\\B 0:+b\n\n# Italic text\nadd-highlighter shared/typst/ regex \\b_.*?_\\b 0:+i\n\n# Code expressions: functions, variables\nadd-highlighter shared/typst/ regex (^|\\h)#(\\w|\\.|-)+ 0:meta\n\n# Bold terms in term lists\nadd-highlighter shared/typst/ regex ^/\\h[^:]*: 0:+b\n\n§\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n \ndefine-command -hidden typst-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # Preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # Cleanup trailing whitespaces from previous line\n        try %{ execute-keys -draft k x s \\h+$ <ret> d }\n    >\n>\n"
  },
  {
    "path": "rc/filetype/vhdl.kak",
    "content": "# Based on IEEE Std 1076‐2019\n\n# Detection\nhook global BufCreate .*[.](vhd[l]?) %[\n    set-option buffer filetype vhdl\n]\n\n# Initialization\nhook global WinSetOption filetype=vhdl %[\n    require-module vhdl\n    set-option window static_words %opt{vhdl_static_words}\n    hook -group vhdl-indent window InsertChar \\n vhdl-indent-on-new-line\n    hook -group vhdl-indent window InsertChar \\) vhdl-indent-on-closing-parenthesis\n    hook -group vhdl-insert window InsertChar \\n vhdl-insert-on-new-line\n    # Cleanup trailing whitespaces on current line insert end.\n    hook -group vhdl-trim-indent window ModeChange pop:insert:.* %[ try %[ execute-keys -draft <semicolon> x s ^\\h+$ <ret> d ] ]\n    hook -once -always window WinSetOption filetype=.* %[ remove-hooks window vhdl-.+ ]\n]\n\nhook -group vhdl-highlight global WinSetOption filetype=vhdl %[\n    add-highlighter window/vhdl ref vhdl\n    hook -once -always window WinSetOption filetype=.* %[ remove-highlighter window/vhdl ]\n]\n\nprovide-module vhdl %§\n\n# Highlighters & Completion\nadd-highlighter shared/vhdl regions\nadd-highlighter shared/vhdl/code default-region group\nadd-highlighter shared/vhdl/comment_line region '--' $ fill comment\nadd-highlighter shared/vhdl/comment region /\\* \\*/ fill comment\n\n# Integer formats\nadd-highlighter shared/vhdl/code/ regex '(?i)\\b0b[01]+l?\\b' 0:value\nadd-highlighter shared/vhdl/code/ regex '(?i)\\b0x[\\da-f]+l?\\b' 0:value\nadd-highlighter shared/vhdl/code/ regex '(?i)\\b0o?[0-7]+l?\\b' 0:value\nadd-highlighter shared/vhdl/code/ regex '(?i)\\b([1-9]\\d*|0)l?\\b' 0:value\n# Float formats\nadd-highlighter shared/vhdl/code/ regex '\\b\\d+[eE][+-]?\\d+\\b' 0:value\nadd-highlighter shared/vhdl/code/ regex '(\\b\\d+)?\\.\\d+\\b' 0:value\nadd-highlighter shared/vhdl/code/ regex '\\b\\d+\\.' 0:value\n# Imaginary formats\nadd-highlighter shared/vhdl/code/ regex '\\b\\d+\\+\\d+[jJ]\\b' 0:value\n\nevaluate-commands %sh[\n    values=\"true false note warning error failure nul\"\n\n    # LRM 5.2.4.1\n    units=\"fs ps ns us ms sec min hr Å nm um mm cm m km\"\n\n    # LRM 16.2\n    predefined_attributes=\"\n        base left right high low ascending length range reverse_range\n        subtype image pos succ pred leftof rightof value val\n        designated_subtype reflect high low index element delayed\n        stable quiet transaction event active last_event last_active\n        last_value driving driving_value simple_name instance_name\n        path_name record signal converse\n    \"\n\n    libraries=\"ieee std work\"\n\n    packages=\"\n        math_real math_complex std_logic_1164 std_logic_textio numeric_bit numeric_std\n        numeric_bit_unsigned numeric_std_unsigned fixed_float_types fixed_generic_pkg\n        fixed_pkg float_generic_pkg float_pkg\n        standard textio env\n    \"\n\n     # LRM 15.10\n    reserved_words=\"\n        abs access after alias all and architecture array assert assume assume_guarantee attribute\n        begin block body buffer bus\n        case component configuration constant context cover\n        default disconnect downto\n        else elsif end entity exit\n        fairness file for force function\n        generate generic group guarded\n        if impure in inertial inout is\n        label library linkage literal loop\n        map mod\n        nand new next nor not null\n        of on open or others out\n        package parameter port postponed procedure process property protected pure\n        range record register reject release rem report restrict restrict_guarantee return rol ror\n        select sequence severity signal shared sla sll sra srl strong subtype\n        then to transport type\n        unaffected units until use\n        variable view vpkg vmode vprop vunit\n        wait when while with\n        xnor xor\n    \"\n\n    types=\"\n        bit bit_vector\n        boolean boolean_vector\n        character\n        file_open_state file_origin_kind\n        integer integer_vector\n        natural positive\n        line line_vector\n        real real_vector\n        std_logic std_logic_vector\n        std_ulogic std_ulogic_vector\n        side\n        signed unsigned\n        string text\n        time time_vector\n    \"\n\n    functions=\"\n        find_leftmost find_rightmost divide reciprocal remainder modulo minimum maximum\n        std_match add_carry scalb\n        resize to_ufixed to_sfixed to_unsigned to_signed to_real to_integer to_slv\n        to_std_logic_vector to_stdlogicvector to_sulv to_std_ulogic_vector to_std_ulogicvector\n        to_01 is_x to_x01 to_ux01 to_x01z\n        ufixed_high ufixed_low sfixed_high sfixed_low to_ufix to_sfix ufix_high ufix_low\n        sfix_high sfix_low\n        write read bwrite binary_write bread binary_read owrite oread octal_write octal_read\n        hwrite hread hex_write hex_read to_string to_bstring to_binary_string to_ostring\n        to_octal_string to_hstring to_hex_string from_string from_bstring from_binary_string\n        from_ostring from_octal_string from_hstring from_hex_string\n        rising_edge falling_edge\n    \"\n\n    join() { sep=$2; eval set -- $1; IFS=\"$sep\"; echo \"$*\"; }\n\n    # Add the language's grammar to the static completion list\n    printf %s\\\\n \"declare-option str-list vhdl_static_words $(join \"${values} ${units} ${predefined_attributes} ${libraries} ${packages} ${reserved_words} ${types} ${functions}\" ' ')\"\n\n     # Highlight keywords\n    printf %s \"\n        add-highlighter shared/vhdl/code/ regex '(?i)\\b($(join \"${values}\" '|'))\\b' 0:value\n        add-highlighter shared/vhdl/code/ regex '(?i)\\b($(join \"${units}\" '|'))\\b' 0:meta\n        add-highlighter shared/vhdl/code/ regex \\\"'(?i)\\b($(join \"${predefined_attributes}\" '|'))\\b\\\" 0:attribute\n        add-highlighter shared/vhdl/code/ regex '(?i)\\b($(join \"${libraries}\" '|'))\\b' 0:builtin\n        add-highlighter shared/vhdl/code/ regex '(?i)\\b($(join \"${packages}\" '|'))\\b' 0:builtin\n        add-highlighter shared/vhdl/code/ regex '(?i)\\b($(join \"${reserved_words}\" '|'))\\b' 0:keyword\n        add-highlighter shared/vhdl/code/ regex '(?i)\\b($(join \"${functions}\" '|'))\\b\\(' 1:builtin\n        add-highlighter shared/vhdl/code/ regex '(?i)\\b($(join \"${types}\" '|'))\\b' 0:type\n        add-highlighter shared/vhdl/code/ regex '^\\h*(@[\\w_.]+))' 1:attribute\n    \"\n]\n\nadd-highlighter shared/vhdl/code/ regex \\(|\\)|\\;|\\.|,|:|\\| 0:attribute\n\nadd-highlighter shared/vhdl/code/ regex \\?\\?|=|/=|<|<=|>|>=|\\?=|\\?/=|\\?<|\\?<=|\\?>|\\?>=|\\+|-|&|\\*|/|:= 0:operator\n\n# Meta values highlight.\n# The values 'U', 'X', 'W', and '–' are metalogical values; they define the behavior of the model itself rather than the behavior of the hardware being synthesized.\nadd-highlighter shared/vhdl/code/ regex \"(?i)'[U|X|W|-]'\" 0:meta\n# Highlight other logical values.\nadd-highlighter shared/vhdl/code/ regex \"(?i)'[0|1|Z|L|H]'\" 0:value\n\n# String\nadd-highlighter shared/vhdl/code/ regex '\"[^\"]*\"' 0:string\n\n# Binary vector.\nadd-highlighter shared/vhdl/code/ regex '[bB]\"[01_]*\"' 0:value\n\n# Octal vector.\nadd-highlighter shared/vhdl/code/ regex '[oO]\"[01234567_]*\"' 0:value\n\n# Hex vector.\nadd-highlighter shared/vhdl/code/ regex '(?i)x\"[0123456789abcdef_]*\"' 0:value\n\ndefine-command -hidden vhdl-insert-on-new-line %[\n    # Handle comment lines.\n    evaluate-commands -itersel %[\n        # Copy '--' comment prefix and following white spaces.\n        try %[\n            # <a-lt> is needed because of \"Preserve previous line indent\" command.\n            try %[ execute-keys -draft k x s ^\\h*--\\h* <ret> y j <a-lt> gh P ]\n        ]\n    ]\n\n    evaluate-commands -save-regs x %[\n        # Save previous line indent in register x.\n        try %[ execute-keys -draft kxs^\\h+<ret>\"xy ] catch %[ reg x '' ]\n\n        # All \"wisely add\" commands share the same concept.\n        # Only \"end if\" has extra comments.\n        # Wisely add \"end if;\".\n        evaluate-commands %[\n            try %[\n                # Validate previous line and that it is not closed yet.\n                execute-keys -draft kx <a-k>^\\h*(?i)((then|(.*:\\h*)?if\\b.*\\bthen)$)<ret> j}ijx <a-K>^<c-r>x(?i)(elsif|else|end)\\b<ret>\n                # Don't add for \"if ... generate\", it requires \"end generate;\".\n                execute-keys -draft kx <a-K>(?i)\\bgenerate\\b<ret>\n                execute-keys -draft o<c-r>xend<space>if<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"end generate;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i).*\\bgenerate$<ret> j}ijx <a-K>^<c-r>x(?i)(begin|elsif|else|end)\\b<ret>\n                # Don't add in case of comment line.\n                execute-keys -draft kx <a-K>^\\h*--<ret>\n                execute-keys -draft o<c-r>xend<space>generate<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"when\" and \"end case;\".\n        evaluate-commands %[\n            try %[\n                # TODO: Case needs special handling.\n                execute-keys -draft kx <a-k>^\\h*(?i)(case|.*\\h*:\\h*case)\\b<ret> jwx <a-K>^<c-r>x(?i)(end|when)<ret>\n                execute-keys -draft <c-r>xo<c-r>xend<space>case<semicolon><esc>kAwhen<space><esc>\n            ]\n        ]\n        # Wisely add \"begin\" and \"end block;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)((block|.*:\\h*block)\\b)<ret> j}ijx <a-K>^<c-r>x(?i)(begin|end)\\b<ret>\n                execute-keys -draft o<c-r>xbegin<ret><c-r>xend<space>block<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"begin\" and \"end process;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)(.*:\\h*)?(postponed\\h+)?process\\b<ret> j}ijx <a-K>^<c-r>x(?i)(begin|end)<ret>\n                execute-keys -draft o<c-r>xbegin<ret><c-r>xend<space>process<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"end loop;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)(.*\\bloop|.*\\h*:\\h*(for|loop))$<ret> j}ijx <a-K>^<c-r>x(?i)(end)<ret>\n                execute-keys -draft o<c-r>xend<space>loop<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"end protected;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)(type\\b.*\\bis\\h+protected)$<ret> j}ijx <a-K>^<c-r>x(?i)(end)<ret>\n                execute-keys -draft o<c-r>xend<space>protected<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"end protected body;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^(?i)(\\h*type\\h+\\w+\\h+is\\h+protected\\h+body$)<ret> j}ijx <a-K>^<c-r>x(?i)end\\h+protected\\h+body\\b<ret>\n                execute-keys -draft o<c-r>xend<space>protected<space>body<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"end record;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)(type\\b.*\\bis\\h+record\\h*)$<ret> j}ijx <a-K>^<c-r>x(?i)(end)<ret>\n                execute-keys -draft o<c-r>xend<space>record<semicolon><esc>\n            ]\n        ]\n        # Wisely add \");\" for \"type ... is (\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)(type\\b.*\\bis\\h+\\(\\h*)$<ret> j}ijx <a-K>^<c-r>x(\\))<ret>\n                execute-keys -draft o<c-r>x)<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"end entity;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^(?i)\\h*entity\\b.*\\bis$<ret> j}ijx <a-K>^<c-r>x(?i)(begin|end)\\b<ret>\n                execute-keys -draft o<c-r>xend<space>entity<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"begin\" and \"end function;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^(?i)(\\h*\\)?\\h*return\\b.*\\bis$)<ret> j}ijx <a-K>^<c-r>x(?i)(begin|end)\\b<ret>\n                execute-keys -draft o<c-r>xbegin<ret><c-r>xend<space>function<semicolon><esc>\n            ]\n            try %[\n                execute-keys -draft kx <a-k>^(?i)(\\h*((pure|impure)\\h+)?function\\b.*\\bis$)<ret> j}ijx <a-K>^<c-r>x(?i)(begin|end)\\b<ret>\n                execute-keys -draft o<c-r>xbegin<ret><c-r>xend<space>function<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"begin\" and \"end procedure;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^(?i)(\\h*procedure\\b.*\\bis$)<ret> j}ijx <a-K>^<c-r>x(?i)\\b(begin|end)\\b<ret>\n                execute-keys -draft o<c-r>xbegin<ret><c-r>xend<space>procedure<semicolon><esc>\n            ]\n            try %[\n                execute-keys -draft kx <a-k>^(?i)\\h*\\)\\h*\\bis$<ret> j}ijx <a-K>^<c-r>x(?i)\\b(begin|end)\\b<ret>\n                # Verify that line with opening parenthesis contains \"procedure\" keyword.\n                execute-keys -draft kx s\\)<ret> <a-m><semicolon> x<a-k> (?i)\\bprocedure\\b<ret>\n                execute-keys -draft o<c-r>xbegin<ret><c-r>xend<space>procedure<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"end package;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^(?i)(package\\b)<ret> j}ijx <a-K>^<c-r>x(?i)(end)\\b<ret>\n                # Make sure it is not package body.\n                execute-keys -draft kx<a-K>(?i)\\bbody\\b<ret>\n                execute-keys -draft oend<space>package<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"end package body;\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^(?i)(package\\h+body\\b)<ret> j}ijx <a-K>^<c-r>x(?i)(end)\\b<ret>\n                execute-keys -draft oend<space>package<space>body<semicolon><esc>\n            ]\n        ]\n        # Wisely add \"begin\" and \"end architecture;\".\n        evaluate-commands %[\n            try %[\n            execute-keys -draft kx <a-k>^(?i)\\h*architecture\\b<ret> j}ijx <a-K>^<c-r>x(?i)(begin|end)\\b<ret>\n            execute-keys -draft o<c-r>xbegin<ret><c-r>xend<space>architecture<semicolon><esc>\n            ]\n        ]\n        # Wisely add \");\" for \"port (\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)port\\h*\\($<ret> j}ijx <a-K>^<c-r>x(\\)\\;)<ret>\n                execute-keys -draft o<c-r>x)<semicolon><esc>\n            ]\n        ]\n        # Wisely add \");\" for \"port map (\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)port\\h+map\\h*\\($<ret> j}ijx <a-K>^<c-r>x(\\)\\;)<ret>\n                execute-keys -draft o<c-r>x)<semicolon><esc>\n            ]\n        ]\n        # Wisely add \");\" for \"generic (\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)generic\\h*\\($<ret> j}ijx <a-K>^<c-r>x(\\)\\;)<ret>\n                execute-keys -draft o<c-r>x)<semicolon><esc>\n            ]\n        ]\n        # Wisely add \")\" for \"generic map (\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)generic\\h+map\\h*\\($<ret> j}ijx <a-K>^<c-r>x(\\))<ret>\n                execute-keys -draft o<c-r>x)<esc>\n            ]\n        ]\n        # Wisely add \") return ;\" for \"[pure|impure] function ... (\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)(pure\\b|impure\\b)?\\h*function\\b.*\\h*\\($<ret> j}ijx <a-K>^<c-r>x(\\)\\h*return.*)<ret>\n                execute-keys -draft o<c-r>x)<space>return<space><semicolon><esc>\n            ]\n        ]\n        # Wisely add \");\" for \"procedure ... (\".\n        evaluate-commands %[\n            try %[\n                execute-keys -draft kx <a-k>^\\h*(?i)procedure\\b.*\\h*\\($<ret> j}ijx <a-K>^<c-r>x(\\)\\h*\\;)<ret>\n                execute-keys -draft o<c-r>x)<semicolon><esc>\n            ]\n        ]\n    ]\n]\n\ndefine-command -hidden vhdl-indent-on-new-line %{\n    evaluate-commands -itersel %{\n        # Align \"then\" to previous \"if|elsif\".\n        evaluate-commands -itersel -save-regs x %[\n            try %[\n                execute-keys -draft k x <a-k> (?i)^\\h*then$ <ret>\n                try %[ execute-keys -draft <a-/>(?i)\\b(if|elsif)\\b<ret>xs^\\h+<ret>\"xy ] catch %[ reg x '' ]\n                try %[ execute-keys -draft k xs^\\h+<ret>d ] catch %[ ]\n                execute-keys -draft kgh i<c-r>x<esc>\n            ]\n        ]\n\n         # Align \"generate\" to previous \"if|for\".\n        evaluate-commands -itersel -save-regs x %[\n            try %[\n                execute-keys -draft k x <a-k> (?i)^\\h*generate$ <ret>\n                try %[ execute-keys -draft <a-/>(?i)\\b(if|for)\\b<ret>xs^\\h+<ret>\"xy ] catch %[ reg x '' ]\n                try %[ execute-keys -draft k xs^\\h+<ret>d ] catch %[ ]\n                execute-keys -draft kgh i<c-r>x<esc>\n            ]\n        ]\n\n         # Preserve previous line indent.\n        try %[ execute-keys -draft <semicolon> K <a-&> ]\n\n         # Cleanup trailing whitespaces from previous line.\n        try %[ execute-keys -draft k x s \\h+$ <ret> d ]\n\n         # Increase indent after some keywords.\n        try %[\n            execute-keys -draft kx<a-k> (?i)\\b(begin|block|body|else|for|generate|if|is|loop|process|protected|record|select|then)$ <ret>\n            # Do not indent if in comment line.\n            execute-keys -draft kx<a-K>(?i)^\\h*--<ret>\n            # Do not indent for \"case ... is\".\n            execute-keys -draft kx<a-K>^\\h*(?i)(case|.*\\h*:\\h*case)\\b<ret>\n            execute-keys -draft <semicolon> <a-gt>\n        ]\n        # Copy the indentation of the matching if.\n        try %{ execute-keys -draft , k x <a-k> ^\\h*(elsif\\b|else$) <ret> gh [c^\\h*(\\S*\\h*:\\h*)?if\\b,\\bend\\sif\\b <ret> x <a-S> 1<a-&> , j K <a-&> }\n\n        # Increase indent after some operators.\n        try %[ execute-keys -draft <semicolon> k x <a-k> (\\(|=>|<=|:=)$ <ret> j <a-gt> ]\n     }\n}\n\ndefine-command vhdl-indent-on-closing-parenthesis %[\n    evaluate-commands -itersel %[\n        # Decrease indent after \")\" at the beginning of line.\n        try %[ execute-keys -draft x <a-k> (^\\h+\\)$) <ret> <a-lt> ]\n    ]\n]\n\n§\n"
  },
  {
    "path": "rc/filetype/void-linux.kak",
    "content": "# Void Linux package template\nhook global BufCreate .*/?srcpkgs/.+/template %{\n    set-option buffer filetype sh\n}\n"
  },
  {
    "path": "rc/filetype/wren.kak",
    "content": "provide-module -override wren %§\n    add-highlighter shared/wren regions\n\n    add-highlighter shared/wren/line_comment region '//' '$' fill comment\n    add-highlighter shared/wren/block_comment region -recurse '/\\*' '/\\*' '\\*/' fill comment\n\n    add-highlighter shared/wren/raw_string region '\"\"\"' '\"\"\"' fill string\n\n    add-highlighter shared/wren/string region '\"' '(?<!\\\\)(\\\\\\\\)*\"' group\n    add-highlighter shared/wren/string/ fill string\n    add-highlighter shared/wren/string/ regex '\\\\([0\"\\\\%abefnrtv]|x[\\dA-Fa-f]{2}|u[\\dA-Fa-f]{4}|U[\\dA-Fa-f]{8})'0:value\n    add-highlighter shared/wren/string/ regex '(?<!\\\\)%\\(.*?\\)' 0:value\n\n    add-highlighter shared/wren/code default-region group\n\n    add-highlighter shared/wren/code/ regex '(?i)([a-z][\\w_]*)\\h*(?=[\\(\\{])'         1:function\n    add-highlighter shared/wren/code/ regex '(?i)([a-z][\\w_]*)=\\(.*?\\)\\h*(?=\\{)'     1:function\n    add-highlighter shared/wren/code/ regex 'class\\h+(?i)([a-z][\\w_]*)\\h*(?=\\{)'     1:type\n    add-highlighter shared/wren/code/ regex 'construct\\h+(?i)([a-z][\\w_]*)\\h*(?=\\()' 1:meta\n    add-highlighter shared/wren/code/ regex 'var\\h+(?i)([a-z][\\w_]*)'                1:variable\n\n    add-highlighter shared/wren/code/ regex '\\b_[\\w_]+' 0:variable\n\n    add-highlighter shared/wren/code/ regex '\\bimport\\b' 0:meta\n    add-highlighter shared/wren/code/ regex '\\b(true|false|null)\\b' 0:value\n    add-highlighter shared/wren/code/ regex '\\b(as|break|class|construct|continue|else|for|foreign|if|in|return|static|super|this|var|while)\\b' 0:keyword\n    add-highlighter shared/wren/code/ regex '\\b(Bool|Class|Fiber|Fn|List|Map|Null|Num|Object|Range|Sequence|String|System)\\b' 0:+b@type\n    add-highlighter shared/wren/code/ regex '(-|!|~|\\*|/|%|\\+|\\.\\.\\.?|<<|>>|&{1,2}|\\^|\\|{1,2}|[<>]=?)|\\bis\\b|[!=]?=|\\?|:)' 0:operator\n\n    add-highlighter shared/wren/code/ regex 'class\\h+([A-Za-z][\\w_]*)\\h+(is\\h+[A-Za-z][\\w_]*)\\h*(?=\\{)' 1:type 2:attribute\n\n    add-highlighter shared/wren/code/ regex '\\b(?i)-?\\d+\\b'               0:value\n    add-highlighter shared/wren/code/ regex '\\b-?0x(?i)[\\da-f]+\\b'        0:value\n    add-highlighter shared/wren/code/ regex '\\b(?i)-?\\d+\\.\\d+\\b'          0:value\n    add-highlighter shared/wren/code/ regex '\\b(?i)-?\\d+\\.\\d+e[+-]?\\d+\\b' 0:value\n\n    add-highlighter shared/wren/code/ regex '^\\h*import\\h*\"(.*?)\"' 1:module\n\n    add-highlighter shared/wren/code/ regex '\\bFn\\.new\\h*(?=[\\{\\(])'    0:+b@value\n    add-highlighter shared/wren/code/ regex '\\bFiber\\.new\\h*(?=[\\{\\(])' 0:+b@value\n    add-highlighter shared/wren/code/ regex '\\bFiber\\.current\\b'        0:+b@value\n    add-highlighter shared/wren/code/ regex '\\bSystem\\.clock\\b'         0:+b@value\n\n    add-highlighter shared/wren/code/ regex '\\bFiber\\.(yield|abort|suspend)\\h*(?=[\\{\\(])'   0:+b@function\n    add-highlighter shared/wren/code/ regex '\\bSystem\\.((print|write)(All)?)\\h*(?=[\\{\\(])'  0:+b@function\n    add-highlighter shared/wren/code/ regex '\\bSystem\\.gc\\h*(?=\\()'                         0:+b@function\n\n    add-highlighter shared/wren/code/ regex '\\bList\\.filled\\h*(?=\\()'    0:+b@function\n    add-highlighter shared/wren/code/ regex '\\b(List|Map)\\.new\\h*(?=\\()' 0:+b@value\n\n    add-highlighter shared/wren/code/ regex '\\bNum\\.fromString\\h*(?=\\()' 0:+b@function\n    add-highlighter shared/wren/code/ regex \\\n        '\\bNum\\.(infinity|nan|pi|tau|largest|smallest|(min|max)SafeInteger)\\b' 0:+b@value\n\n    add-highlighter shared/wren/code/ regex '\\bObject\\.same\\h*(?=\\()'                 0:+b@function\n    add-highlighter shared/wren/code/ regex '\\bString\\.from(Byte|CodePoint)\\h*(?=\\()' 0:+b@function\n\n    declare-option str-list wren_static_words \\\n        'import' 'true' 'false' 'null' 'as' 'break' 'class' 'construct' 'continue' 'else' 'for' 'foreign' 'if' 'in' 'return' 'static' 'super' 'this' \\\n        'var' 'while' 'Bool' 'Class' 'Fiber' 'Fn' 'List' 'Map' 'Null' 'Num' 'Object' 'Range' 'Sequence' 'String' 'System'\n§\n\nhook global BufCreate (.*/)?.*\\.wren %{ set-option buffer filetype wren }\n\nhook -group wren-highlight global WinSetOption filetype=wren %{\n    require-module wren\n    add-highlighter window/wren ref wren\n    hook -once -always window WinSetOption filetype=.* %{\n        remove-highlighter window/wren\n    }\n}\n\nhook global WinSetOption filetype=wren %{\n    require-module wren\n\n    set-option window static_words %opt{wren_static_words}\n\n    hook window ModeChange pop:insert:.* -group wren-trim-indent %{ try %{ execute-keys -draft xs^\\h+$<ret>d } }\n    hook window InsertChar \\n -group wren-indent wren-indent-on-new-line\n    hook window InsertChar \\{ -group wren-indent wren-indent-on-opening-curly-brace\n    hook window InsertChar \\} -group wren-indent wren-indent-on-closing-curly-brace\n    hook window InsertChar \\n -group wren-comment-insert wren-insert-comment-on-new-line\n    hook window InsertChar \\n -group wren-closing-delimiter-insert wren-insert-closing-delimiter-on-new-line\n}\n\ndefine-command -hidden wren-indent-on-new-line %~\n    evaluate-commands -draft -itersel %=\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon>K<a-&> }\n        # cleanup trailing white spaces on the previous line\n        try %{ execute-keys -draft kx s \\h+$ <ret>d }\n        try %<\n            try %{ # line comment\n                execute-keys -draft kx s ^\\h*// <ret>\n            } catch %{ # block comment\n                execute-keys -draft <a-?> /\\* <ret> <a-K>\\*/<ret>\n            }\n        > catch %<\n            # indent after lines with an unclosed { or (\n            try %< execute-keys -draft [c[({],[)}] <ret> <a-k> \\A[({][^\\n]*\\n[^\\n]*\\n?\\z <ret> j<a-gt> >\n            # deindent closing brace(s) when after cursor\n            try %[ execute-keys -draft x <a-k> ^\\h*[})] <ret> gh / [})] <ret> m <a-S> 1<a-&> ]\n        >\n    =\n~\n\ndefine-command -hidden wren-indent-on-opening-curly-brace %[\n    # align indent with opening paren when { is entered on a new line after the closing paren\n    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \\A\\(.*\\)\\h*\\n\\h*\\{\\z <ret> s \\A|.\\z <ret> 1<a-&> ]\n]\n\ndefine-command -hidden wren-indent-on-closing-curly-brace %[\n    # align to opening curly brace when alone on a line\n    try %[ execute-keys -itersel -draft <a-h><a-k>^\\h+\\}$<ret>hms\\A|.\\z<ret>1<a-&> ]\n]\n\ndefine-command -hidden wren-insert-comment-on-new-line %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # copy // comments prefix and following white spaces\n        try %{ execute-keys -draft <semicolon><c-s>kx s ^\\h*\\K/{2,}\\h* <ret> y<c-o>P<esc> }\n    ]\n]\n\ndefine-command -hidden wren-insert-closing-delimiter-on-new-line %[\n    evaluate-commands -no-hooks -draft -itersel %[\n        # Wisely add '}'.\n        evaluate-commands -save-regs x %[\n            # Save previous line indent in register x.\n            try %[ execute-keys -draft kxs^\\h+<ret>\"xy ] catch %[ reg x '' ]\n            try %[\n                # Validate previous line and that it is not closed yet.\n                execute-keys -draft kx <a-k>^<c-r>x.*\\{\\h*\\(?\\h*$<ret> j}iJx <a-K>^<c-r>x\\)?\\h*\\}<ret>\n                # Insert closing '}'.\n                execute-keys -draft o<c-r>x}<esc>\n                # Delete trailing '}' on the line below the '{'.\n                execute-keys -draft xs\\}$<ret>d\n            ]\n        ]\n\n        # Wisely add ')'.\n        evaluate-commands -save-regs x %[\n            # Save previous line indent in register x.\n            try %[ execute-keys -draft kxs^\\h+<ret>\"xy ] catch %[ reg x '' ]\n            try %[\n                # Validate previous line and that it is not closed yet.\n                execute-keys -draft kx <a-k>^<c-r>x.*\\(\\h*$<ret> J}iJx <a-K>^<c-r>x\\)<ret>\n                # Insert closing ')'.\n                execute-keys -draft o<c-r>x)<esc>\n                # Delete trailing ')' on the line below the '('.\n                execute-keys -draft xs\\)\\h*\\}?\\h*$<ret>d\n            ]\n        ]\n    ]\n]\n"
  },
  {
    "path": "rc/filetype/yaml.kak",
    "content": "# http://yaml.org\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](ya?ml) %{\n    set-option buffer filetype yaml\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=yaml %{\n    require-module yaml\n\n    hook window ModeChange pop:insert:.* -group yaml-trim-indent yaml-trim-indent\n    hook window InsertChar \\n -group yaml-insert yaml-insert-on-new-line\n    hook window InsertChar \\n -group yaml-indent yaml-indent-on-new-line\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window yaml-.+ }\n}\n\nhook -group yaml-highlight global WinSetOption filetype=yaml %{\n    add-highlighter window/yaml ref yaml\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/yaml }\n}\n\n\nprovide-module yaml %{\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/yaml regions\nadd-highlighter shared/yaml/code      default-region group\nadd-highlighter shared/yaml/double_string region '\"' (?<!\\\\)(\\\\\\\\)*\"       fill string\nadd-highlighter shared/yaml/single_string region \"'\" \"'\"                   fill string\nadd-highlighter shared/yaml/comment       region '(?:^| )#' $              fill comment\n\nadd-highlighter shared/yaml/code/ regex ^(---|\\.\\.\\.)$ 0:meta\nadd-highlighter shared/yaml/code/ regex ^(\\h*:\\w*) 0:keyword\nadd-highlighter shared/yaml/code/ regex \\b(true|false|null)\\b 0:value\nadd-highlighter shared/yaml/code/ regex ^\\h*-?\\h*(\\S+): 1:attribute\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden yaml-trim-indent %{\n    # remove trailing white spaces\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden yaml-insert-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # copy '#' comment prefix and following white spaces\n        try %{ execute-keys -draft k x s ^\\h*\\K#\\h* <ret> y gh j P }\n    }\n}\n\ndefine-command -hidden yaml-indent-on-new-line %{\n    evaluate-commands -draft -itersel %{\n        # preserve previous line indent\n        try %{ execute-keys -draft <semicolon> K <a-&> }\n        # filter previous line\n        try %{ execute-keys -draft k : yaml-trim-indent <ret> }\n        # indent after :\n        try %{ execute-keys -draft , k x <a-k> :$ <ret> j <a-gt> }\n        # indent after -\n        try %{ execute-keys -draft , k x <a-k> ^\\h*- <ret> j <a-gt> }\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/filetype/zig.kak",
    "content": "# zig syntax highlighting for kakoune (https://ziglang.org)\n#\n# based off of https://github.com/ziglang/zig.vim/blob/master/syntax/zig.vim\n# as well as https://ziglang.org/documentation/master/#Grammar\n\n# Detection\n# ‾‾‾‾‾‾‾‾‾\n\nhook global BufCreate .*[.](zig|zon) %{\n  set-option buffer filetype zig\n}\n\n# Initialization\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nhook global WinSetOption filetype=zig %<\n    require-module zig\n    hook window ModeChange pop:insert:.* -group zig-trim-indent zig-trim-indent\n    hook window InsertChar \\n -group zig-insert zig-insert-on-new-line\n    hook window InsertChar \\n -group zig-indent zig-indent-on-new-line\n    hook window InsertChar \\} -group zig-indent zig-indent-on-closing\n\n    hook -once -always window WinSetOption filetype=.* %< remove-hooks window zig-.+ >\n>\n\nhook -group zig-highlight global WinSetOption filetype=zig %{\n    require-module zig\n    add-highlighter window/zig ref zig\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/zig }\n}\n\nprovide-module zig %§\n\n# Highlighters\n# ‾‾‾‾‾‾‾‾‾‾‾‾\n\nadd-highlighter shared/zig regions\nadd-highlighter shared/zig/code default-region group\n\nadd-highlighter shared/zig/doc_comment region '///(?=[^/])' '$' fill documentation\nadd-highlighter shared/zig/comment region '//' '$' fill comment\n\n# strings and characters\nadd-highlighter shared/zig/string region '\"' (?<!\\\\)(\\\\\\\\)*\" group\nadd-highlighter shared/zig/string/ fill string\nadd-highlighter shared/zig/string/ regex '(?:\\\\n|\\\\r|\\\\t|\\\\\\\\|\\\\''|\\\\\"|\\\\x[0-9a-fA-F]{2}|\\\\u\\{[0-9a-fA-F]+\\})' 0:meta\n\nadd-highlighter shared/zig/character region \"'\" (?<!\\\\)(\\\\\\\\)*' group\nadd-highlighter shared/zig/character/ fill string\nadd-highlighter shared/zig/character/ regex '(?:\\\\n|\\\\r|\\\\t|\\\\\\\\|\\\\''|\\\\\"|\\\\x[0-9a-fA-F]{2}|\\\\u\\{[0-9a-fA-F]+\\})' 0:meta\n\nadd-highlighter shared/zig/multiline_string region '\\\\\\\\' '$' fill string\n\n# attributes\nadd-highlighter shared/zig/code/ regex '\\b(?:const|var|extern|packed|export|pub|noalias|inline|noinline|comptime|callconv|volatile|allowzero|addrspace|align|linksection|threadlocal)\\b' 0:attribute\n# structures\nadd-highlighter shared/zig/code/ regex '\\b(?:struct|enum|union|error|opaque)\\b' 0:attribute\n\n# statement\nadd-highlighter shared/zig/code/ regex '\\b(?:break|return|continue|asm|defer|errdefer|unreachable|try|catch|suspend|nosuspend|resume)\\b' 0:keyword\n# conditional\nadd-highlighter shared/zig/code/ regex '\\b(?:if|else|switch|and|or|orelse)\\b' 0:keyword\n# repeat\nadd-highlighter shared/zig/code/ regex '\\b(?:while|for)\\b' 0:keyword\n# other keywords\nadd-highlighter shared/zig/code/ regex '\\b(?:fn|test)\\b' 0:keyword\n\n# types\nadd-highlighter shared/zig/code/ regex '\\b(?:bool|f16|f32|f64|f80|f128|void|noreturn|type|anyerror|anyframe|anytype|anyopaque)\\b' 0:type\nadd-highlighter shared/zig/code/ regex '\\b(?:i0|u0|isize|usize|comptime_int|comptime_float)\\b' 0:type\nadd-highlighter shared/zig/code/ regex '\\b(?:[iu][1-9]\\d*)\\b' 0:type\nadd-highlighter shared/zig/code/ regex '\\b(?:c_char|c_short|c_ushort|c_int|c_uint|c_long|c_ulong|c_longlong|c_ulonglong|c_longdouble)\\b' 0:type\n\n# primitive values\nadd-highlighter shared/zig/code/ regex '\\b(?:true|false|null|undefined)\\b' 0:value\n\n# integer literals\nadd-highlighter shared/zig/code/ regex '\\b[0-9](_?[0-9])*\\b' 0:value\nadd-highlighter shared/zig/code/ regex '\\b0x[0-9a-fA-F](_?[0-9a-fA-F])*\\b' 0:value\nadd-highlighter shared/zig/code/ regex '\\b0o[0-7](_?[0-7])*\\b' 0:value\nadd-highlighter shared/zig/code/ regex '\\b0b[01](_?[01])*\\b' 0:value\n\n# float literals\nadd-highlighter shared/zig/code/ regex '\\b[0-9]+\\.[0-9]+(?:[eE][-+]?[0-9]+)?\\b' 0:value\nadd-highlighter shared/zig/code/ regex '\\b0x[0-9a-fA-F]+\\.[0-9a-fA-F]+(?:[pP][-+]?[0-9a-fA-F]+)?\\b' 0:value\nadd-highlighter shared/zig/code/ regex '\\b[0-9]+\\.?[eE][-+]?[0-9]+\\b' 0:value\nadd-highlighter shared/zig/code/ regex '\\b0x[0-9a-fA-F]+\\.?[eE][-+]?[0-9a-fA-F]+\\b' 0:value\n\n# operators\nadd-highlighter shared/zig/code/ regex '(?:\\+|-|\\*|/|%|=|<|>|&|\\||\\^|~|\\?|!)' 0:operator\n\n# builtin functions\nadd-highlighter shared/zig/code/ regex \"@(?:addrSpaceCast|addWithOverflow|alignCast|alignOf|as|atomicLoad|atomicRmw|atomicStore|bitCast|bitOffsetOf|bitSizeOf|branchHint|breakpoint|mulAdd|byteSwap|bitReverse|offsetOf|call|cDefine|cImport|cInclude|clz|cmpxchgStrong|cmpxchgWeak|compileError|compileLog|constCast|ctz|cUndef|cVaArg|cVaCopy|cVaEnd|cVaStart|disableInstrumentation|disableIntrinsics|divExact|divFloor|divTrunc|embedFile|enumFromInt|errorFromInt|errorName|errorReturnTrace|errorCast|export|extern|field|fieldParentPtr|FieldType|floatCast|floatFromInt|frameAddress|hasDecl|hasField|import|inComptime|intCast|intFromBool|intFromEnum|intFromError|intFromFloat|intFromPtr|max|memcpy|memmove|memset|min|wasmMemorySize|wasmMemoryGrow|mod|mulWithOverflow|panic|popCount|prefetch|ptrCast|ptrFromInt|rem|returnAddress|select|setEvalBranchQuota|setFloatMode|setRuntimeSafety|shlExact|shlWithOverflow|shrExact|shuffle|sizeOf|splat|reduce|src|sqrt|sin|cos|tan|exp|exp2|log|log2|log10|abs|floor|ceil|trunc|round|subWithOverflow|tagName|This|trap|truncate|Type|typeInfo|typeName|TypeOf|unionInit|Vector|volatileCast|workGroupId|workGroupSize|workItemId)\\b\" 0:meta\n\n# Commands\n# ‾‾‾‾‾‾‾‾\n\ndefine-command -hidden zig-trim-indent %{\n    # delete trailing whitespace\n    try %{ execute-keys -draft -itersel x s \\h+$ <ret> d }\n}\n\ndefine-command -hidden zig-insert-on-new-line %<\n    try %[\n        evaluate-commands -draft -save-regs '/\"' %[\n            # copy // or /// comments prefix or \\\\ string literal prefix and following whitespace\n            execute-keys -save-regs '' k x1s^\\h*((///?|\\\\\\\\)+\\h*)<ret> y\n            try %[\n                # if the previous comment isn't empty, create a new one\n                execute-keys x<a-K>^\\h*//+\\h*$<ret> jxs^\\h*<ret>P\n            ] catch %[\n                # if there is no text in the previous comment, remove it completely\n                execute-keys d\n            ]\n        ]\n\n        # trim trailing whitespace on the previous line\n        try %[ execute-keys -draft k x s\\h+$<ret> d ]\n    ]\n>\n\ndefine-command -hidden zig-indent-on-new-line %<\n    evaluate-commands -draft -itersel %<\n        # preserve indent level\n        try %< execute-keys -draft <semicolon> K <a-&> >\n        try %<\n            # only if we didn't copy a comment or multiline string\n            execute-keys -draft x <a-K> ^\\h*(//|\\\\\\\\) <ret>\n            # indent after lines ending in {\n            try %< execute-keys -draft k x <a-k> \\{\\h*$ <ret> j <a-gt> >\n            # deindent closing } when after cursor\n            try %< execute-keys -draft x <a-k> ^\\h*\\} <ret> gh / \\} <ret> m <a-S> 1<a-&> >\n        >\n        # filter previous line\n        try %< execute-keys -draft k : zig-trim-indent <ret> >\n    >\n>\n\ndefine-command -hidden zig-indent-on-closing %<\n    # align lone } to indent level of opening line\n    try %< execute-keys -draft -itersel <a-h> <a-k> ^\\h*\\}$ <ret> h m <a-S> 1<a-&> >\n>\n\n§\n"
  },
  {
    "path": "rc/tools/autorestore.asciidoc",
    "content": "= Automatically restore unsaved work after a crash.\n\nWhen Kakoune crashes, it automatically writes out unsaved changes to backup\nfiles with predictable names. When you edit a file, if such a backup file\nexists, this plugin will automatically load the content of the backup file\ninstead.\n\nBy default, backup files are deleted when restored. You can set the\n`autorestore_purge_restored` option to `false` to prevent this.\n\nIf you don't want backups to be restored automatically, use the\n`autorestore-disable` command to disable the feature for the current session,\nor put it in your `kakrc` to disable the feature forever.\n"
  },
  {
    "path": "rc/tools/autorestore.kak",
    "content": "declare-option -docstring %{\n        Remove backups once they've been restored\n\n        See `:doc autorestore` for details.\n    } \\\n    bool autorestore_purge_restored true\n\n## Insert the content of the backup file into the current buffer, if a suitable one is found\ndefine-command autorestore-restore-buffer \\\n    -docstring %{\n        Restore the backup for the current file if it exists\n\n        See `:doc autorestore` for details.\n    } \\\n%{\n    evaluate-commands %sh{\n        buffer_basename=\"${kak_buffile##*/}\"\n        buffer_dirname=$(dirname \"${kak_buffile}\")\n\n        if [ -f \"${kak_buffile}\" ]; then\n            newer=$(find \"${buffer_dirname}\"/\".${buffer_basename}.kak.\"* -newer \"${kak_buffile}\" -exec ls -1t {} + 2>/dev/null | head -n 1)\n            older=$(find \"${buffer_dirname}\"/\".${buffer_basename}.kak.\"* \\! -newer \"${kak_buffile}\" -exec ls -1t {} + 2>/dev/null | head -n 1)\n        else\n            # New buffers that were never written to disk.\n            newer=$(ls -1t \"${buffer_dirname}\"/\".${buffer_basename}.kak.\"* 2>/dev/null | head -n 1)\n            older=\"\"\n        fi\n\n        if [ -z \"${newer}\" ]; then\n            if [ -n \"${older}\" ]; then\n                printf %s\\\\n \"\n                    echo -debug Old backup file(s) found: will not restore ${older} .\n                \"\n            fi\n            exit\n        fi\n\n        printf %s\\\\n \"\n            ## Replace the content of the buffer with the content of the backup file\n            echo -debug Restoring file: ${newer}\n\n            execute-keys -draft %{%d!cat<space>\\\"${newer}\\\"<ret>jd}\n\n            ## If the backup file has to be removed, issue the command once\n            ## the current buffer has been saved\n            ## If the autorestore_purge_restored option has been unset right after the\n            ## buffer was restored, do not remove the backup\n            hook -group autorestore buffer BufWritePost '${kak_buffile}' %{\n                nop %sh{\n                    if [ \\\"\\${kak_opt_autorestore_purge_restored}\\\" = true ];\n                    then\n                        rm -f \\\"${buffer_dirname}/.${buffer_basename}.kak.\\\"*\n                    fi\n                }\n            }\n        \"\n    }\n}\n\n## Remove all the backups that have been created for the current buffer\ndefine-command autorestore-purge-backups \\\n    -docstring %{\n        Remove all the backups of the current buffer\n\n        See `:doc autorestore` for details.\n    } \\\n%{\n    evaluate-commands %sh{\n        [ ! -f \"${kak_buffile}\" ] && exit\n\n        buffer_basename=\"${kak_bufname##*/}\"\n        buffer_dirname=$(dirname \"${kak_bufname}\")\n\n        rm -f \"${buffer_dirname}/.${buffer_basename}.kak.\"*\n\n        printf %s\\\\n \"\n            echo -markup {Information}Backup files removed.\n            \"\n    }\n}\n\n## If for some reason, backup files need to be ignored\ndefine-command autorestore-disable \\\n    -docstring %{\n        Disable automatic backup recovering\n\n        See `:doc autorestore` for details.\n    } \\\n%{\n    remove-hooks global autorestore\n}\n\nhook -group autorestore global BufCreate .* %{ autorestore-restore-buffer }\n"
  },
  {
    "path": "rc/tools/autowrap.kak",
    "content": "declare-option -docstring \"maximum amount of characters per line, after which a newline character will be inserted\" \\\n    int autowrap_column 80\n\ndeclare-option -docstring %{\n    when enabled, paragraph formatting will reformat the whole paragraph in which characters are being inserted\n    This can potentially break formatting of documents containing markup (e.g. markdown)\n} bool autowrap_format_paragraph no\ndeclare-option -docstring %{\n    command to which the paragraphs to wrap will be passed\n    all occurences of '%c' are replaced with `autowrap_column`\n} str autowrap_fmtcmd 'fold -s -w %c'\n\ndefine-command -hidden autowrap-cursor %{ evaluate-commands -save-regs '/\"|^@m' %{\n    try %{\n        ## if the line isn't too long, do nothing\n        execute-keys -draft \"x<a-k>^[^\\n]{%opt{autowrap_column},}[^\\n]<ret>\"\n\n        try %{\n            reg m \"%val{selections_desc}\"\n\n            ## if we're adding characters past the limit, just wrap them around\n            execute-keys -draft \"<a-h><a-k>.{%opt{autowrap_column}}\\h*[^\\s]*<ret>1s(\\h+)[^\\h]*\\z<ret>c<ret>\"\n        } catch %{\n            ## if we're adding characters in the middle of a sentence, use\n            ## the `fmtcmd` command to wrap the entire paragraph\n            evaluate-commands %sh{\n                if [ \"${kak_opt_autowrap_format_paragraph}\" = true ] \\\n                    && [ -n \"${kak_opt_autowrap_fmtcmd}\" ]; then\n                    format_cmd=$(printf %s \"${kak_opt_autowrap_fmtcmd}\" \\\n                                 | sed \"s/%c/${kak_opt_autowrap_column}/g\")\n                    printf %s \"\n                        evaluate-commands -draft %{\n                            execute-keys '<a-]>px<a-j>|${format_cmd}<ret>'\n                            try %{ execute-keys s\\h+$<ret> d }\n                        }\n                        select '${kak_main_reg_m}'\n                    \"\n                fi\n            }\n        }\n    }\n} }\n\ndefine-command autowrap-enable -docstring \"Automatically wrap the lines in which characters are inserted\" %{\n    hook -group autowrap window InsertChar [^\\n] autowrap-cursor\n}\n\ndefine-command autowrap-disable -docstring \"Disable automatic line wrapping\" %{\n    remove-hooks window autowrap\n}\n"
  },
  {
    "path": "rc/tools/clang.kak",
    "content": "hook -once global BufSetOption filetype=(c|cpp) %{\n    require-module clang\n}\n\nprovide-module clang %[\n\ndeclare-option -docstring \"options to pass to the `clang` shell command\" \\\n    str clang_options\n\ndeclare-option -docstring \"directory from which to invoke clang\" \\\n    str clang_directory\n\ndeclare-option -hidden completions clang_completions\ndeclare-option -hidden line-specs clang_flags\ndeclare-option -hidden line-specs clang_errors\n\ndefine-command -params ..1 \\\n    -docstring %{\n        Parse the contents of the current buffer\n        The syntaxic errors detected during parsing are shown when auto-diagnostics are enabled\n    } clang-parse %{\n    evaluate-commands %sh{\n        dir=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-clang.XXXXXXXX)\n        mkfifo ${dir}/fifo\n        printf %s\\\\n \"\n            evaluate-commands -no-hooks write -sync -method replace ${dir}/buf\n            evaluate-commands -draft %{\n                edit! -fifo ${dir}/fifo -debug *clang-output*\n                set-option buffer filetype make\n                set-option buffer jump_current_line 0\n                hook -once -always buffer BufCloseFifo .* %{ nop %sh{ rm -r ${dir} } }\n            }\"\n\n        # this runs in a detached shell, asynchronously, so that kakoune does\n        # not hang while clang is running. As completions references a cursor\n        # position and a buffer timestamp, only valid completions should be\n        # displayed.\n        ((\n            trap - INT QUIT\n            until [ -f ${dir}/buf ]; do :; done # wait for the buffer to be written\n\n            if [ -n \"$kak_opt_clang_directory\" ]; then\n                cd \"$kak_opt_clang_directory\"\n            fi\n            case ${kak_opt_filetype} in\n                (c) ft=c ;;\n                (cpp) ft=c++ ;;\n                (obj-c) ft=objective-c ;;\n                (*) ft=c++ ;;\n            esac\n\n            if [ \"$1\" = \"-complete\" ]; then\n                pos=-:${kak_cursor_line}:${kak_cursor_column}\n                header=\"${kak_cursor_line}.${kak_cursor_column}@${kak_timestamp}\"\n                compl=$(clang++ -x ${ft} -fsyntax-only ${kak_opt_clang_options} \\\n                    -Xclang -code-completion-brief-comments -Xclang -code-completion-at=${pos} - < ${dir}/buf 2> ${dir}/stderr |\n                        awk -F ': ' '\n                            /^COMPLETION:/ && $2 !~ /[(,](Hidden|Inaccessible)[),]/ {\n                                 candidate=$3\n                                 gsub(/[[<{]#[^#]*#[]>}]/, \"\", candidate)\n                                 gsub(/~/, \"~~\", candidate)\n                                 gsub(/\\|/, \"\\\\|\", candidate)\n\n                                 gsub(/[[{<]#|#[]}>]/, \" \", $3)\n                                 gsub(/:: /, \"::\", $3)\n                                 gsub(/ ,/, \",\", $3)\n                                 gsub(/^ +| +$/, \"\", $3)\n                                 docstring=$4 ? $3 \"\\n\" $4 : $3\n\n                                 gsub(/~|!/, \"&&\", docstring)\n                                 gsub(/\\|/, \"\\\\|\", docstring)\n                                 if (candidate in candidates)\n                                     candidates[candidate]=candidates[candidate] \"\\n\" docstring\n                                 else\n                                     candidates[candidate]=docstring\n                            }\n                            END {\n                                for (candidate in candidates) {\n                                    menu=candidate\n                                    gsub(/(^|[^[:alnum:]_])(operator|new|delete)($|[^{}_[:alnum:]]+)/, \"{keyword}&{}\", menu)\n                                    gsub(/(^|[[:space:]])(int|size_t|bool|char|unsigned|signed|long)($|[[:space:]])/, \"{type}&{}\", menu)\n                                    gsub(/[^{}_[:alnum:]]+/, \"{operator}&{}\", menu)\n                                    printf \"%%~%s|info -style menu %!%s!|%s~ \", candidate, candidates[candidate], menu\n                                }\n                            }')\n                printf %s\\\\n \"evaluate-commands -client ${kak_client} echo 'clang completion done'\n                      set-option 'buffer=${kak_buffile}' clang_completions ${header} ${compl}\" | kak -p ${kak_session}\n            else\n                clang++ -x ${ft} -fsyntax-only ${kak_opt_clang_options} - < ${dir}/buf 2> ${dir}/stderr\n                printf %s\\\\n \"evaluate-commands -client ${kak_client} echo 'clang parsing done'\" | kak -p ${kak_session}\n            fi\n\n            flags=$(cat ${dir}/stderr | sed -Ene \"\n                        /^<stdin>:[0-9]+:([0-9]+:)? (fatal )?error/ { s/^<stdin>:([0-9]+):.*/'\\1|{red}█'/; p }\n                        /^<stdin>:[0-9]+:([0-9]+:)? warning/ { s/^<stdin>:([0-9]+):.*/'\\1|{yellow}█'/; p }\n                    \" | paste -s -d ' ' -)\n\n            errors=$(cat ${dir}/stderr | sed -Ene \"\n                        /^<stdin>:[0-9]+:([0-9]+:)? ((fatal )?error|warning)/ {\n                            s/'/''/g; s/^<stdin>:([0-9]+):([0-9]+:)? (.*)/'\\1|\\3'/; p\n                        }\" | sort -n | paste -s -d ' ' -)\n\n            sed -e \"s|<stdin>|${kak_bufname}|g\" < ${dir}/stderr > ${dir}/fifo\n\n            printf %s\\\\n \"set-option 'buffer=${kak_buffile}' clang_flags ${kak_timestamp} ${flags}\n                  set-option 'buffer=${kak_buffile}' clang_errors ${kak_timestamp} ${errors}\" | kak -p ${kak_session}\n        ) & ) > /dev/null 2>&1 < /dev/null\n    }\n}\n\ndefine-command clang-complete -docstring \"Complete the current selection\" %{ clang-parse -complete }\n\ndefine-command -hidden clang-show-completion-info %[ try %[\n    evaluate-commands -draft %[\n        execute-keys ,{( <a-k> ^\\( <ret> b <a-k> \\A\\w+\\z <ret>\n        evaluate-commands %sh[\n            desc=$(printf %s\\\\n \"${kak_opt_clang_completions}\" | sed -e \"{ s/\\([^\\\\]\\):/\\1\\n/g }\" | sed -ne \"/^${kak_selection}|/ { s/^[^|]\\+|//; s/|.*$//; s/\\\\\\:/:/g; p }\")\n            if [ -n \"$desc\" ]; then\n                printf %s\\\\n \"evaluate-commands -client $kak_client %{info -anchor ${kak_cursor_line}.${kak_cursor_column} -style above %{${desc}}}\"\n            fi\n    ] ]\n] ]\n\ndefine-command clang-enable-autocomplete -docstring \"Enable automatic clang completion\" %{\n    set-option window completers \"option=clang_completions\" %opt{completers}\n    hook window -group clang-autocomplete InsertIdle .* %{\n        try %{\n            execute-keys -draft <a-h><a-k>(\\.|->|::).\\z<ret>\n            echo 'completing...'\n            clang-complete\n        }\n        clang-show-completion-info\n    }\n    alias window complete clang-complete\n}\n\ndefine-command clang-disable-autocomplete -docstring \"Disable automatic clang completion\" %{\n    evaluate-commands %sh{ printf \"set-option window completers %s\\n\" $(printf %s \"${kak_opt_completers}\" | sed -e \"s/'option=clang_completions'//g\") }\n    remove-hooks window clang-autocomplete\n    unalias window complete clang-complete\n}\n\ndefine-command -hidden clang-show-error-info %{\n    update-option buffer clang_errors # Ensure we are up to date with buffer changes\n    evaluate-commands %sh{\n        eval \"set -- ${kak_quoted_opt_clang_errors}\"\n        shift # skip timestamp\n        desc=$(for error in \"$@\"; do\n            if [ \"${error%%|*}\" = \"$kak_cursor_line\" ]; then\n                printf '%s\\n' \"${error##*|}\"\n            fi\n        done)\n        if [ -n \"$desc\" ]; then\n            desc=$(printf %s \"${desc}\" | sed \"s/'/''/g\")\n            printf \"info -anchor %d.%d '%s'\\n\" \"${kak_cursor_line}\" \"${kak_cursor_column}\" \"${desc}\"\n        fi\n    } }\n\ndefine-command clang-enable-diagnostics -docstring %{\n    Activate automatic error reporting and diagnostics\n    Information about the analysis will be shown after the buffer has been parsed with the clang-parse function\n} %{\n    add-highlighter window/clang_flags flag-lines default clang_flags\n    hook window -group clang-diagnostics NormalIdle .* %{ clang-show-error-info }\n    hook window -group clang-diagnostics WinSetOption ^clang_errors=.* %{ info; clang-show-error-info }\n}\n\ndefine-command clang-disable-diagnostics -docstring \"Disable automatic error reporting and diagnostics\" %{\n    remove-highlighter window/clang_flags\n    remove-hooks window clang-diagnostics\n}\n\ndefine-command clang-diagnostics-next -docstring \"Jump to the next line that contains an error\" %{\n    update-option buffer clang_errors # Ensure we are up to date with buffer changes\n    evaluate-commands %sh{\n        eval \"set -- ${kak_quoted_opt_clang_errors}\"\n        shift # skip timestamp\n        unset line\n        unset first_line\n        for error in \"$@\"; do\n            candidate=${error%%|*}\n            first_line=${first_line-$candidate}\n            if [ \"$candidate\" -gt $kak_cursor_line ]; then\n                line=$candidate\n                break\n            fi\n        done\n        line=${line-$first_line}\n        if [ -n \"$line\" ]; then\n            printf %s\\\\n \"execute-keys ${line} g\"\n        else\n            echo \"fail no next clang diagnostic\"\n        fi\n    } }\n\n]\n"
  },
  {
    "path": "rc/tools/comment.kak",
    "content": "# Line comments\n# If the language has no line comments, set to ''\ndeclare-option -docstring \"characters inserted at the beginning of a commented line\" \\\n    str comment_line '#'\n\n# Block comments\ndeclare-option -docstring \"characters inserted before a commented block\" \\\n    str comment_block_begin\ndeclare-option -docstring \"characters inserted after a commented block\" \\\n    str comment_block_end\n\n# Default comments for all languages\nhook global BufSetOption filetype=asciidoc %{\n    set-option buffer comment_line '//'\n    set-option buffer comment_block_begin '////'\n    set-option buffer comment_block_end '////'\n}\n\nhook global BufSetOption filetype=(c|cpp|dart|gluon|go|hjson|java|javascript|json5|kdl|objc|odin|php|pony|protobuf|ron|rust|sass|scala|scss|swift|typescript|typst|groovy) %{\n    set-option buffer comment_line '//'\n    set-option buffer comment_block_begin '/*'\n    set-option buffer comment_block_end '*/'\n}\n\nhook global BufSetOption filetype=(cabal|haskell|moon|idris|elm|dhall|purescript) %{\n    set-option buffer comment_line '--'\n    set-option buffer comment_block_begin '{-'\n    set-option buffer comment_block_end '-}'\n}\n\nhook global BufSetOption filetype=clojure %{\n    set-option buffer comment_line '#_'\n    set-option buffer comment_block_begin '(comment '\n    set-option buffer comment_block_end ')'\n}\n\nhook global BufSetOption filetype=janet %{\n    set-option buffer comment_line '#'\n    set-option buffer comment_block_begin '(comment '\n    set-option buffer comment_block_end ')'\n}\n\nhook global BufSetOption filetype=coffee %{\n    set-option buffer comment_block_begin '###'\n    set-option buffer comment_block_end '###'\n}\n\nhook global BufSetOption filetype=(capnp|conf|hyprlang|ssh) %{\n    set-option buffer comment_line '#'\n}\n\nhook global BufSetOption filetype=css %{\n    set-option buffer comment_line ''\n    set-option buffer comment_block_begin '/*'\n    set-option buffer comment_block_end '*/'\n}\n\nhook global BufSetOption filetype=d %{\n    set-option buffer comment_line '//'\n    set-option buffer comment_block_begin '/+'\n    set-option buffer comment_block_end '+/'\n}\n\nhook global BufSetOption filetype=(fennel|gas|ini) %{\n    set-option buffer comment_line ';'\n}\n\nhook global BufSetOption filetype=haml %{\n    set-option buffer comment_line '-#'\n}\n\nhook global BufSetOption filetype=(html|xml) %{\n    set-option buffer comment_line ''\n    set-option buffer comment_block_begin '<!--'\n    set-option buffer comment_block_end '-->'\n}\n\nhook global BufSetOption filetype=julia %{\n    set-option buffer comment_block_begin '#='\n    set-option buffer comment_block_end '=#'\n}\n\nhook global BufSetOption filetype=(erlang|latex|mercury) %{\n    set-option buffer comment_line '%'\n}\n\nhook global BufSetOption filetype=ledger %{\n    set-option buffer comment_line ';'\n}\n\nhook global BufSetOption filetype=(lisp|scheme) %{\n    set-option buffer comment_line ';'\n    set-option buffer comment_block_begin '#|'\n    set-option buffer comment_block_end '|#'\n}\n\nhook global BufSetOption filetype=lua %{\n    set-option buffer comment_line '--'\n    set-option buffer comment_block_begin '--[['\n    set-option buffer comment_block_end ']]'\n}\n\nhook global BufSetOption filetype=markdown %{\n    set-option buffer comment_line ''\n    set-option buffer comment_block_begin '[//]: # \"'\n    set-option buffer comment_block_end '\"'\n}\n\nhook global BufSetOption filetype=(ocaml|coq) %{\n    set-option buffer comment_line ''\n    set-option buffer comment_block_begin '(* '\n    set-option buffer comment_block_end ' *)'\n}\n\nhook global BufSetOption filetype=((free|object)?pascal|delphi) %{\n    set-option buffer comment_line '//'\n    set-option buffer comment_block_begin '{'\n    set-option buffer comment_block_end '}'\n}\n\nhook global BufSetOption filetype=perl %{\n    set-option buffer comment_block_begin '#['\n    set-option buffer comment_block_end ']'\n}\n\nhook global BufSetOption filetype=(pug|zig|cue|hare|gleam) %{\n    set-option buffer comment_line '//'\n}\n\nhook global BufSetOption filetype=python %{\n    set-option buffer comment_block_begin \"'''\"\n    set-option buffer comment_block_end \"'''\"\n}\n\nhook global BufSetOption filetype=r %{\n    set-option buffer comment_line '#'\n}\n\nhook global BufSetOption filetype=ragel %{\n    set-option buffer comment_line '%%'\n    set-option buffer comment_block_begin '%%{'\n    set-option buffer comment_block_end '}%%'\n}\n\nhook global BufSetOption filetype=ruby %{\n    set-option buffer comment_block_begin '^begin='\n    set-option buffer comment_block_end '^=end'\n}\n\nhook global BufSetOption filetype=sql %{\n    set-option buffer comment_line '--'\n    set-option buffer comment_block_begin '/*'\n    set-option buffer comment_block_end '*/'\n}\n\ndefine-command comment-block -docstring '(un)comment selections using block comments' %{\n    evaluate-commands %sh{\n        if [ -z \"${kak_opt_comment_block_begin}\" ] || [ -z \"${kak_opt_comment_block_end}\" ]; then\n            echo \"fail \\\"The 'comment_block' options are empty, could not comment the selection\\\"\"\n        fi\n    }\n    evaluate-commands -save-regs '\"/' -draft %{\n        # Keep non-empty selections\n        execute-keys <a-K>\\A\\s*\\z<ret>\n\n        try %{\n            # Assert that the selection has been commented\n            set-register / \"\\A\\Q%opt{comment_block_begin}\\E.*\\Q%opt{comment_block_end}\\E\\n*\\z\"\n            execute-keys \"s<ret>\"\n            # Uncomment it\n            set-register / \"\\A\\Q%opt{comment_block_begin}\\E|\\Q%opt{comment_block_end}\\E\\n*\\z\"\n            execute-keys s<ret>d\n        } catch %{\n            # Comment the selection\n            set-register '\"' \"%opt{comment_block_begin}\"\n            execute-keys -draft P\n            set-register '\"' \"%opt{comment_block_end}\"\n            execute-keys p\n        }\n    }\n}\n\ndefine-command comment-line -docstring '(un)comment selected lines using line comments' %{\n    evaluate-commands %sh{\n        if [ -z \"${kak_opt_comment_line}\" ]; then\n            echo \"fail \\\"The 'comment_line' option is empty, could not comment the line\\\"\"\n        fi\n    }\n    evaluate-commands -save-regs '\"/' -draft %{\n        # Select the content of the lines, without indentation\n        execute-keys <a-s>gi<a-l>\n\n        try %{\n            # Keep non-empty lines\n            execute-keys <a-K>\\A\\s*\\z<ret>\n        }\n\n        try %{\n            set-register / \"\\A\\Q%opt{comment_line}\\E\\h?\"\n\n            try %{\n                # See if there are any uncommented lines in the selection\n                execute-keys -draft <a-K><ret>\n\n                # There are uncommented lines, so comment everything\n                set-register '\"' \"%opt{comment_line} \"\n                align-selections-left\n                execute-keys P\n            } catch %{\n                # All lines were commented, so uncomment everything\n                execute-keys s<ret>d\n            }\n        }\n    }\n}\n\ndefine-command align-selections-left -docstring 'extend selections to the left to align with the leftmost selected column' %{\n    evaluate-commands %sh{\n        leftmost_column=$(echo \"$kak_selections_desc\" | tr ' ' '\\n' | cut -d',' -f1 | cut -d'.' -f2 | sort -n | head -n1)\n        aligned_selections=$(echo \"$kak_selections_desc\" | sed -E \"s/\\.[0-9]+,/.$leftmost_column,/g\")\n        echo \"select $aligned_selections\"\n    }\n}\n"
  },
  {
    "path": "rc/tools/ctags.kak",
    "content": "# Kakoune CTags support script\n#\n# This script requires the readtags command available in universal-ctags\n\ndeclare-option -docstring \"minimum characters before triggering autocomplete\" \\\n    int ctags_min_chars 3\n\ndeclare-option -docstring \"list of paths to tag files to parse when looking up a symbol\" \\\n    str-list ctagsfiles 'tags'\n\ndeclare-option -hidden completions ctags_completions\n\ndeclare-option -docstring \"shell command to run\" str readtagscmd \"readtags\"\n\ndefine-command -params ..1 \\\n    -shell-script-candidates %{\n        realpath() { ( cd \"$(dirname \"$1\")\"; printf \"%s/%s\\n\" \"$(pwd -P)\" \"$(basename \"$1\")\" ) }\n        eval \"set -- $kak_quoted_opt_ctagsfiles\"\n        for candidate in \"$@\"; do\n            [ -f \"$candidate\" ] && realpath \"$candidate\"\n        done | awk '!x[$0]++;' | # remove duplicates\n        while read -r tags; do\n            namecache=\"${tags%/*}/.kak.${tags##*/}.namecache\"\n            if [ -z \"$(find \"$namecache\" -prune -newer \"$tags\")\" ]; then\n                cut -f 1 \"$tags\" | grep -v '^!' | uniq > \"$namecache\"\n            fi\n            cat \"$namecache\"\n        done\n    } \\\n    -docstring %{\n        ctags-search [<symbol>]: jump to a symbol's definition\n        If no symbol is passed then the current selection is used as symbol name\n    } \\\n    ctags-search %[ require-module menu; evaluate-commands %sh[\n        realpath() { ( cd \"$(dirname \"$1\")\"; printf \"%s/%s\\n\" \"$(pwd -P)\" \"$(basename \"$1\")\" ) }\n        export tagname=\"${1:-${kak_selection}}\"\n        eval \"set -- $kak_quoted_opt_ctagsfiles\"\n        for candidate in \"$@\"; do\n            [ -f \"$candidate\" ] && realpath \"$candidate\"\n        done | awk '!x[$0]++' | # remove duplicates\n        while read -r tags; do\n            printf '!TAGROOT\\t%s\\n' \"$(realpath \"${tags%/*}\")/\"\n            ${kak_opt_readtagscmd} -t \"$tags\" \"$tagname\"\n        done | awk -F '\\t|\\n' '\n            /^!TAGROOT\\t/ { tagroot=$2 }\n            /[^\\t]+\\t[^\\t]+\\t\\/\\^.*\\$?\\// {\n                line = $0; sub(\".*\\t/\\\\^\", \"\", line); sub(\"\\\\$?/$\", \"\", line);\n                menu_info = line; gsub(\"!\", \"!!\", menu_info); gsub(/^[\\t ]+/, \"\", menu_info); gsub(/\\t/, \" \", menu_info);\n                keys = line; gsub(/</, \"<lt>\", keys); gsub(/\\t/, \"<c-v><c-i>\", keys); gsub(\"!\", \"!!\", keys); gsub(\"&\", \"&&\", keys); gsub(\"#\", \"##\", keys); gsub(\"\\\\|\", \"||\", keys); gsub(\"\\\\\\\\/\", \"/\", keys);\n                menu_item = $2; gsub(\"!\", \"!!\", menu_item);\n                edit_path = path($2); gsub(\"&\", \"&&\", edit_path); gsub(\"#\", \"##\", edit_path); gsub(\"\\\\|\", \"||\", edit_path);\n                select = $1; gsub(/</, \"<lt>\", select); gsub(/\\t/, \"<c-v><c-i>\", select); gsub(\"!\", \"!!\", select); gsub(\"&\", \"&&\", select); gsub(\"#\", \"##\", select); gsub(\"\\\\|\", \"||\", select);\n                out = out \"%!\" menu_item \": \" menu_info \"! %!evaluate-commands %# try %& edit -existing %|\" edit_path \"|; execute-keys %|/\\\\Q\" keys \"<ret>vc| & catch %& fail unable to find tag &; try %& execute-keys %|s\\\\Q\" select \"<ret>| & # !\"\n            }\n            /[^\\t]+\\t[^\\t]+\\t[0-9]+/ {\n                menu_item = $2; gsub(\"!\", \"!!\", menu_item);\n                select = $1; gsub(/</, \"<lt>\", select); gsub(/\\t/, \"<c-v><c-i>\", select); gsub(\"!\", \"!!\", select); gsub(\"&\", \"&&\", select); gsub(\"#\", \"##\", select); gsub(\"\\\\|\", \"||\", select);\n                menu_info = $3; gsub(\"!\", \"!!\", menu_info);\n                edit_path = path($2); gsub(\"!\", \"!!\", edit_path); gsub(\"#\", \"##\", edit_path); gsub(\"&\", \"&&\", edit_path); gsub(\"\\\\|\", \"||\", edit_path);\n                line_number = $3;\n                out = out \"%!\" menu_item \": \" menu_info \"! %!evaluate-commands %# try %& edit -existing %|\" edit_path \"|; execute-keys %|\" line_number \"gx| & catch %& fail unable to find tag &; try %& execute-keys %|s\\\\Q\" select \"<ret>| & # !\"\n            }\n            END { print ( length(out) == 0 ? \"fail no such tag \" ENVIRON[\"tagname\"] : \"menu -auto-single \" out ) }\n            # Ensure x is an absolute file path, by prepending with tagroot\n            function path(x) { return x ~/^\\// ? x : tagroot x }'\n    ]]\n\ndefine-command ctags-complete -docstring \"Complete the current selection\" %{\n    nop %sh{\n        (\n            header=\"${kak_cursor_line}.${kak_cursor_column}@${kak_timestamp}\"\n            compl=$(\n                eval \"set -- $kak_quoted_opt_ctagsfiles\"\n                for ctagsfile in \"$@\"; do\n                    ${kak_opt_readtagscmd} -p -t \"$ctagsfile\" ${kak_selection}\n                done | awk '{ uniq[$1]++ } END { for (elem in uniq) printf \" %s||%s\", elem, elem }'\n            )\n            printf %s\\\\n \"evaluate-commands -client ${kak_client} set-option buffer=${kak_bufname} ctags_completions ${header}${compl}\" | \\\n                kak -p ${kak_session}\n        ) > /dev/null 2>&1 < /dev/null &\n    }\n}\n\ndefine-command ctags-funcinfo -docstring \"Display ctags information about a selected function\" %{\n    evaluate-commands -draft %{\n        try %{\n            execute-keys '[(;B<a-k>[a-zA-Z_]+\\(<ret><a-;>'\n            evaluate-commands %sh{\n                f=${kak_selection%?}\n                sig='\\tsignature:(.*)'\n                csn='\\t(class|struct|namespace):(\\S+)'\n                sigs=$(${kak_opt_readtagscmd} -e -Q '(eq? $kind \"f\")' \"${f}\" | sed -Ee \"s/^.*${csn}.*${sig}$/\\3 [\\2::${f}]/ ;t ;s/^.*${sig}$/\\1 [${f}]/\")\n                if [ -n \"$sigs\" ]; then\n                    printf %s\\\\n \"evaluate-commands -client ${kak_client} %{info -anchor $kak_cursor_line.$kak_cursor_column -style above '$sigs'}\"\n                fi\n            }\n        }\n    }\n}\n\ndefine-command ctags-enable-autoinfo -docstring \"Automatically display ctags information about function\" %{\n     hook window -group ctags-autoinfo NormalIdle .* ctags-funcinfo\n     hook window -group ctags-autoinfo InsertIdle .* ctags-funcinfo\n}\n\ndefine-command ctags-disable-autoinfo -docstring \"Disable automatic ctags information displaying\" %{ remove-hooks window ctags-autoinfo }\n\ndeclare-option -docstring \"shell command to run\" \\\n    str ctagscmd \"ctags -R --fields=+S\"\ndeclare-option -docstring \"path to the directory in which the tags file will be generated\" str ctagspaths \".\"\n\ndefine-command ctags-generate -docstring 'Generate tag file asynchronously' %{\n    echo -markup \"{Information}launching tag generation in the background\"\n    nop %sh{ (\n        trap - INT QUIT\n        while ! mkdir .tags.kaklock 2>/dev/null; do sleep 1; done\n        trap 'rmdir .tags.kaklock' EXIT\n\n        if ${kak_opt_ctagscmd} -f .tags.kaktmp ${kak_opt_ctagspaths}; then\n            mv .tags.kaktmp tags\n            msg=\"tags generation complete\"\n        else\n            msg=\"tags generation failed\"\n        fi\n\n        printf %s\\\\n \"evaluate-commands -client $kak_client echo -markup '{Information}${msg}'\" | kak -p ${kak_session}\n    ) > /dev/null 2>&1 < /dev/null & }\n}\n\ndefine-command ctags-update-tags -docstring 'Update tags for the given file' %{\n    nop %sh{ (\n        trap - INT QUIT\n        while ! mkdir .tags.kaklock 2>/dev/null; do sleep 1; done\n            trap 'rmdir .tags.kaklock' EXIT\n\n        if ${kak_opt_ctagscmd} -f .file_tags.kaktmp $kak_bufname; then\n            export LC_COLLATE=C LC_ALL=C # ensure ASCII sorting order\n            # merge the updated tags tags with the general tags (filtering out out of date tags from it) into the target file\n            grep -Fv \"$(printf '\\t%s\\t' \"$kak_bufname\")\" tags | grep -v '^!' | sort --merge - .file_tags.kaktmp >> .tags.kaktmp\n            rm .file_tags.kaktmp\n            mv .tags.kaktmp tags\n            msg=\"tags updated for $kak_bufname\"\n        else\n            msg=\"tags update failed for $kak_bufname\"\n        fi\n\n        printf %s\\\\n \"evaluate-commands -client $kak_client echo -markup '{Information}${msg}'\" | kak -p ${kak_session}\n    ) > /dev/null 2>&1 < /dev/null & }\n}\n\ndefine-command ctags-enable-autocomplete -docstring \"Enable automatic ctags completion\" %{\n    set-option window completers \"option=ctags_completions\" %opt{completers}\n    hook window -group ctags-autocomplete InsertIdle .* %{\n        try %{\n            evaluate-commands -draft %{ # select previous word >= ctags_min_chars\n                execute-keys \",b_<a-k>.{%opt{ctags_min_chars},}<ret>\"\n                ctags-complete          # run in draft context to preserve selection\n            }\n        }\n    }\n}\n\ndefine-command ctags-disable-autocomplete -docstring \"Disable automatic ctags completion\" %{\n    evaluate-commands %sh{\n        printf \"set-option window completers %s\\n\" $(printf %s \"${kak_opt_completers}\" | sed -e \"s/'option=ctags_completions'//g\")\n    }\n    remove-hooks window ctags-autocomplete\n}\n"
  },
  {
    "path": "rc/tools/doc.asciidoc",
    "content": "= Kakoune's online documentation\n\nThis is Kakoune's online documentation system.\n\nTo see what documentation topics are available, type `:doc` and look at the\ncompletion menu.  To view a particular topic, type its name or select it\nfrom the completion menu.  Then hit Enter.\n\nDocumentation will be displayed in the client named in the `docsclient` option.\n\n== Using the documentation browser\n\nDocumentation buffers are like any other buffer, so you can scroll through\nthem as normal, search within a topic with `/`, etc.  However, they can also\ncontain links: <<doc#demonstration-target,like this>>.  Links can be followed\nby moving the cursor onto them and pressing Enter.  If a link takes you to\na different documentation topic, you can return to the original by using the\n`:buffer` command.\n\n== Writing documentation\n\nDocumentation must be in AsciiDoc format, with the extension `.asciidoc`.\nIt must be stored somewhere within <<doc#sources,the documentation search\npath>>.  Kakoune's built-in documentation renderer does not necessarily\nsupport every feature, so don't go overboard with formatting.\n\nTo create a link to another documentation topic, the URL should be the topic's\nname, just like `:doc` uses.  Because topics are identified only by their\nbasename, you should take care that your topic's name does not conflict with\nany of the names used either by other plugins or by Kakoune's standard library.\n\n== Sources\n\nThe `:doc` command searches within the following locations for\ndocuments in the AsciiDoc format (`*.asciidoc`):\n\n* The user plugin directory, `\"%val{config}/autoload\"`\n* The system documentation directory, `\"%val{runtime}/doc\"`\n* The system plugin directory, `\"%val{runtime}/rc\"`\n\nIt searches recursively, and follows symlinks.\n\n== Demonstration target\n\nWell done! You can <<doc#using-the-documentation-browser,go back now>>!\n"
  },
  {
    "path": "rc/tools/doc.kak",
    "content": "declare-option -docstring \"name of the client in which documentation is to be displayed\" \\\n    str docsclient\n\ndeclare-option -hidden range-specs doc_render_ranges\ndeclare-option -hidden range-specs doc_links\ndeclare-option -hidden range-specs doc_anchors\n\ndefine-command -hidden -params 4 doc-render-regex %{\n    evaluate-commands -draft %{ try %{\n        execute-keys <percent> s %arg{1} <ret>\n        execute-keys -draft s %arg{2} <ret> d\n        execute-keys \"%arg{3}\"\n        evaluate-commands %sh{\n            face=\"$4\"\n            eval \"set -- $kak_quoted_selections_desc\"\n            ranges=\"\"\n            for desc in \"$@\"; do ranges=\"$ranges '$desc|$face'\"; done\n            echo \"update-option buffer doc_render_ranges\"\n            echo \"set-option -add buffer doc_render_ranges $ranges\"\n        }\n    } }\n}\n\ndefine-command -hidden doc-parse-links %{\n    evaluate-commands -draft %{ try %{\n        execute-keys <percent> s <lt><lt>(.*?),.*?<gt><gt> <ret>\n        execute-keys -draft s <lt><lt>.*,|<gt><gt> <ret> d\n        execute-keys H\n        set-option buffer doc_links %val{timestamp}\n        update-option buffer doc_render_ranges\n        evaluate-commands -itersel %{\n            set-option -add buffer doc_links \"%val{selection_desc}|%reg{1}\"\n            set-option -add buffer doc_render_ranges \"%val{selection_desc}|default+u\"\n        }\n    } }\n}\n\ndefine-command -hidden doc-parse-anchors %{\n    evaluate-commands -draft %{ try %{\n        set-option buffer doc_anchors %val{timestamp}\n        # Find sections as add them as imlicit anchors\n        execute-keys <percent> s ^={2,}\\h+([^\\n]+)$ <ret>\n        evaluate-commands -itersel %{\n            set-option -add buffer doc_anchors \"%val{selection_desc}|%sh{printf '%s' \"\"$kak_main_reg_1\"\" | tr '[A-Z ]' '[a-z-]'}\"\n        }\n\n        # Parse explicit anchors and remove their text\n        execute-keys <percent> s \\[\\[(.*?)\\]\\]\\s* <ret>\n        evaluate-commands -itersel %{\n            set-option -add buffer doc_anchors \"%val{selection_desc}|%reg{1}\"\n        }\n        execute-keys d\n        update-option buffer doc_anchors\n    } }\n}\n\ndefine-command -hidden doc-jump-to-anchor -params 1 %{\n    update-option buffer doc_anchors\n    evaluate-commands %sh{\n        anchor=\"$1\"\n        eval \"set -- $kak_quoted_opt_doc_anchors\"\n\n        shift\n        for range in \"$@\"; do\n            if [ \"${range#*|}\" = \"$anchor\" ]; then\n                printf '%s\\n'  \"select '${range%|*}'; execute-keys vv\"\n                exit\n            fi\n        done\n        printf \"fail No such anchor '%s'\\n\" \"${anchor}\"\n    }\n}\n\ndefine-command -hidden doc-follow-link %{\n    update-option buffer doc_links\n    evaluate-commands %sh{\n        eval \"set -- $kak_quoted_opt_doc_links\"\n        for link in \"$@\"; do\n            printf '%s\\n' \"$link\"\n        done | awk -v FS='[.,|#]' '\n            BEGIN {\n                l=ENVIRON[\"kak_cursor_line\"];\n                c=ENVIRON[\"kak_cursor_column\"];\n            }\n            l >= $1 && c >= $2 && l <= $3 && c <= $4 {\n                if (NF == 6) {\n                    print \"doc \" $5\n                    if ($6 != \"\") {\n                        print \"doc-jump-to-anchor %{\" $6 \"}\"\n                    }\n                } else {\n                    print \"doc-jump-to-anchor %{\" $5 \"}\"\n                }\n                exit\n            }\n        '\n    }\n}\n\ndefine-command -params 1 -hidden doc-render %{\n    edit! -scratch \"*doc-%sh{basename $1 .asciidoc}*\"\n    execute-keys \"!cat '%arg{1}'<ret>gg\"\n\n    doc-parse-anchors\n\n    # Join paragraphs together\n    try %{\n        execute-keys -draft '%S\\n{2,}|(?<lt>=\\+)\\n|^[^\\n]+::\\n|^\\h*[*-]\\h+<ret>' \\\n            <a-K>^\\h*-{2,}(\\n|\\z)<ret> S\\n\\z<ret> <a-k>\\n<ret> <a-j>\n    }\n\n    # Remove some line end markers\n    try %{ execute-keys -draft <percent> s \\h*(\\+|:{2,})$ <ret> d }\n\n    # Setup the doc_render_ranges option\n    set-option buffer doc_render_ranges %val{timestamp}\n    doc-render-regex \\B(?<!\\\\)\\*(?=\\S)[^\\n]+?(?<=\\S)(?<!\\\\)\\*\\B \\A|.\\z 'H' default+b\n    doc-render-regex \\b(?<!\\\\)_(?=\\S)[^\\n]+?(?<=\\S)(?<!\\\\)_\\b \\A|.\\z 'H' default+i\n    doc-render-regex \\B(?<!\\\\)`(?=\\S)[^\\n]+?(?<=\\S)`\\B \\A|.\\z 'H' mono\n    doc-render-regex ^=\\h+[^\\n]+ ^=\\h+ '~' title\n    doc-render-regex ^={2,}\\h+[^\\n]+ ^={2,}\\h+ '' header\n    doc-render-regex ^\\h*-{2,}\\n\\h*.*?^\\h*-{2,}\\n ^\\h*-{2,}\\n '' block\n\n    doc-parse-links\n\n    # Remove escaping of * and `\n    try %{ execute-keys -draft <percent> s \\\\((?=\\*)|(?=`)) <ret> d }\n    # Go to beginning of file\n    execute-keys 'gg'\n\n    set-option buffer readonly true\n    add-highlighter buffer/ ranges doc_render_ranges\n    add-highlighter buffer/ wrap -word -indent\n    map buffer normal <ret> :doc-follow-link<ret>\n}\n\ndefine-command doc -params 0..2 -docstring %{\n        doc <topic> [<keyword>]: open a buffer containing documentation about a given topic\n        An optional keyword argument can be passed to the function, which will be automatically selected in the documentation\n\n        See `:doc doc` for details.\n    } %{\n    evaluate-commands %sh{\n        topic=\"doc\"\n        if [ $# -ge 1 ]; then\n            topic=\"$1\"\n        fi\n        page=$(\n            find -L \\\n                \"${kak_config}/autoload/\" \\\n                \"${kak_runtime}/doc/\" \\\n                \"${kak_runtime}/rc/\" \\\n                -type f -name \"$topic.asciidoc\" 2>/dev/null |\n                head -1\n        )\n        if [ -f \"${page}\" ]; then\n            jump_cmd=\"\"\n            if [ $# -eq 2 ]; then\n                jump_cmd=\"doc-jump-to-anchor '$2'\"\n            fi\n            printf %s\\\\n \"evaluate-commands -try-client %opt{docsclient} %{ doc-render ${page}; ${jump_cmd} }\"\n        else\n            printf 'fail No such doc file: %s\\n' \"$topic.asciidoc\"\n        fi\n    }\n}\n\ncomplete-command -menu doc shell-script-candidates %{\n    case \"$kak_token_to_complete\" in\n        0)\n            find -L \\\n                \"${kak_config}/autoload/\" \\\n                \"${kak_runtime}/doc/\" \\\n                \"${kak_runtime}/rc/\" \\\n                -type f -name \"*.asciidoc\" 2>/dev/null |\n                sed 's,.*/,,; s/\\.[^.]*$//';;\n        1)\n            page=$(\n                find -L \\\n                    \"${kak_config}/autoload/\" \\\n                    \"${kak_runtime}/doc/\" \\\n                    \"${kak_runtime}/rc/\" \\\n                    -type f -name \"$1.asciidoc\" 2>/dev/null |\n                    head -1\n            )\n            if [ -f \"${page}\" ]; then\n                awk '\n                    /^==+ +/ { sub(/^==+ +/, \"\"); print }\n                    /^\\[\\[[^\\]]+\\]\\]/ { sub(/^\\[\\[/, \"\"); sub(/\\]\\].*/, \"\"); print }\n                ' < $page | tr '[A-Z ]' '[a-z-]'\n            fi;;\n    esac | sort\n}\n\nalias global help doc\n"
  },
  {
    "path": "rc/tools/fifo.kak",
    "content": "provide-module fifo %{\n\ndefine-command -params .. -docstring %{\n    fifo [-name <name>] [-scroll] [-script <script>] [--] <args>...: run command in a fifo buffer\n    if <script> is used, eval it with <args> as '$@', else pass arguments directly to the shell\n} fifo %{ evaluate-commands %sh{\n    name='*fifo*'\n    while true; do\n        case \"$1\" in\n            \"-scroll\") scroll=\"-scroll\"; shift ;;\n            \"-script\") script=\"$2\"; shift 2 ;;\n            \"-name\") name=\"$2\"; shift 2 ;;\n            \"--\") shift; break ;;\n            *) break ;;\n        esac\n    done\n    output=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-fifo.XXXXXXXX)/fifo\n    mkfifo ${output}\n    if [ -n \"$script\" ]; then\n        ( eval \"$script\" > ${output} 2>&1 & ) > /dev/null 2>&1 < /dev/null\n    else\n        ( \"$@\" > ${output} 2>&1 & ) > /dev/null 2>&1 < /dev/null\n    fi\n\n    printf %s\\\\n \"\n            edit! -fifo ${output} ${scroll} ${name}\n            hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }\n        \"\n    }}\n\ncomplete-command fifo shell\n\n}\n\nhook -once global KakBegin .* %{ require-module fifo }\n"
  },
  {
    "path": "rc/tools/format.kak",
    "content": "declare-option -docstring \"shell command used for the 'format-selections' and 'format-buffer' commands\" \\\n    str formatcmd\n\ndefine-command format-buffer -docstring \"Format the contents of the buffer\" %{\n    evaluate-commands -draft %{\n        execute-keys '%'\n        format-selections\n    }\n}\n\ndefine-command format-selections -docstring \"Format the selections individually\" %{\n    evaluate-commands %sh{\n        if [ -z \"${kak_opt_formatcmd}\" ]; then\n            echo \"fail 'The option ''formatcmd'' must be set'\"\n        fi\n    }\n    evaluate-commands -draft -no-hooks -save-regs 'e|' %{\n        set-register e nop\n        set-register '|' %{\n            format_in=\"$(mktemp \"${TMPDIR:-/tmp}\"/kak-formatter.XXXXXX)\"\n            format_out=\"$(mktemp \"${TMPDIR:-/tmp}\"/kak-formatter.XXXXXX)\"\n\n            cat > \"$format_in\"\n            eval \"$kak_opt_formatcmd\" < \"$format_in\" > \"$format_out\"\n            if [ $? -eq 0 ]; then\n                cat \"$format_out\"\n            else\n                echo \"set-register e fail formatter returned an error (exit code $?)\" >\"$kak_command_fifo\"\n                cat \"$format_in\"\n            fi\n            rm -f \"$format_in\" \"$format_out\"\n        }\n        execute-keys '|<ret>'\n        %reg{e}\n    }\n}\n\nalias global format format-buffer\n"
  },
  {
    "path": "rc/tools/git.kak",
    "content": "declare-option -docstring \"name of the client in which documentation is to be displayed\" \\\n    str docsclient\n\ndeclare-option -docstring \"git diff added character\" \\\n    str git_diff_add_char \"▊\"\n\ndeclare-option -docstring \"git diff modified character\" \\\n    str git_diff_mod_char \"▊\"\n\ndeclare-option -docstring \"git diff deleted character\" \\\n    str git_diff_del_char \"_\"\n\ndeclare-option -docstring \"git diff top deleted character\" \\\n    str git_diff_top_char \"‾\"\n\nhook -group git-log-highlight global WinSetOption filetype=git-log %{\n    add-highlighter window/git-log group\n    add-highlighter window/git-log/ regex '^([*|\\\\ /_.-])*' 0:keyword\n    add-highlighter window/git-log/ regex '^( ?[*|\\\\ /_.-])*\\h{,3}(commit )?(\\b[0-9a-f]{4,40}\\b)' 2:keyword 3:comment\n    add-highlighter window/git-log/ regex '^( ?[*|\\\\ /_.-])*\\h{,3}([a-zA-Z_-]+:) (.*?)$' 2:variable 3:value\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/git-log }\n}\n\nhook global WinSetOption filetype=diff %{\n    try %{\n        execute-keys -draft %{/^diff --git\\b<ret>}\n        evaluate-commands %sh{\n            if [ -n \"$(git ls-files -- \"${kak_buffile}\")\" ]; then\n                echo fail\n            fi\n        }\n        set-option buffer filetype git-diff\n    }\n}\n\nhook -group git-diff-highlight global WinSetOption filetype=(git-diff|git-log) %{\n    require-module diff\n    add-highlighter %exp{window/%val{hook_param_capture_1}-ref-diff} ref diff\n    hook -once -always window WinSetOption filetype=.* %exp{\n        remove-highlighter window/%val{hook_param_capture_1}-ref-diff\n    }\n}\n\nhook global WinSetOption filetype=(?:git-diff|git-log) %{\n    map buffer normal <ret> %exp{:git-diff-goto-source # %val{hook_param}<ret>} -docstring 'Jump to source from git diff'\n    hook -once -always window WinSetOption filetype=.* %exp{\n        unmap buffer normal <ret> %%{:git-diff-goto-source # %val{hook_param}<ret>}\n    }\n}\n\nhook -group git-status-highlight global WinSetOption filetype=git-status %{\n    add-highlighter window/git-status group\n    add-highlighter window/git-status/ regex '^## ' 0:comment\n    add-highlighter window/git-status/ regex '^## (\\S*[^\\s\\.@])' 1:green\n    add-highlighter window/git-status/ regex '^## (\\S*[^\\s\\.@])(\\.\\.+)(\\S*[^\\s\\.@])' 1:green 2:comment 3:red\n    add-highlighter window/git-status/ regex '^(##) (No commits yet on) (\\S*[^\\s\\.@])' 1:comment 2:Default 3:green\n    add-highlighter window/git-status/ regex '^## \\S+ \\[[^\\n]*ahead (\\d+)[^\\n]*\\]' 1:green\n    add-highlighter window/git-status/ regex '^## \\S+ \\[[^\\n]*behind (\\d+)[^\\n]*\\]' 1:red\n    add-highlighter window/git-status/ regex '^(?:([Aa])|([Cc])|([Dd!?])|([MUmu])|([Rr])|([Tt]))[ !\\?ACDMRTUacdmrtu]\\h' 1:green 2:blue 3:red 4:yellow 5:cyan 6:cyan\n    add-highlighter window/git-status/ regex '^[ !\\?ACDMRTUacdmrtu](?:([Aa])|([Cc])|([Dd!?])|([MUmu])|([Rr])|([Tt]))\\h' 1:green 2:blue 3:red 4:yellow 5:cyan 6:cyan\n    add-highlighter window/git-status/ regex '^R[ !\\?ACDMRTUacdmrtu] [^\\n]+( -> )' 1:cyan\n    add-highlighter window/git-status/ regex '^\\h+(?:((?:both )?modified:)|(added:|new file:)|(deleted(?: by \\w+)?:)|(renamed:)|(copied:))(?:.*?)$' 1:yellow 2:green 3:red 4:cyan 5:blue 6:magenta\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/git-status }\n}\n\nhook -group git-show-branch-highlight global WinSetOption filetype=git-show-branch %{\n    add-highlighter window/git-show-branch group\n    add-highlighter window/git-show-branch/ regex '(\\*)|(\\+)|(!)' 1:red 2:green 3:green\n    add-highlighter window/git-show-branch/ regex '(!\\D+\\{0\\}\\])|(!\\D+\\{1\\}\\])|(!\\D+\\{2\\}\\])|(!\\D+\\{3\\}\\])' 1:red 2:green 3:yellow 4:blue\n    add-highlighter window/git-show-branch/ regex '(\\B\\+\\D+\\{0\\}\\])|(\\B\\+\\D+\\{1\\}\\])|(\\B\\+\\D+\\{2\\}\\])|(\\B\\+\\D+\\{3\\}\\])|(\\B\\+\\D+\\{1\\}\\^\\])' 1:red 2:green 3:yellow 4:blue 5:magenta\n\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/git-show-branch}\n}\n\ndeclare-option -hidden line-specs git_blame_flags\ndeclare-option -hidden line-specs git_blame_index\ndeclare-option -hidden str git_blame\ndeclare-option -hidden str git_blob\ndeclare-option -hidden line-specs git_diff_flags\ndeclare-option -hidden int-list git_hunk_list\n\ndefine-command -params 1.. \\\n    -docstring %{\n        git [<arguments>]: git wrapping helper\n        All the optional arguments are forwarded to the git utility\n        Available commands:\n            add\n            apply      - run \"patch git apply [<arguments>]\"; if buffile is\n                         tracked, use the changes to selected lines instead\n            blame      - toggle blame annotations\n            blame-jump - show the commit that added the line at cursor\n            checkout\n            commit\n            diff\n            edit\n            grep\n            hide-diff\n            init\n            log\n            next-hunk\n            prev-hunk\n            reset\n            rm\n            show\n            show-branch\n            show-diff\n            status\n            update-diff\n    } -shell-script-candidates %{\n    if [ $kak_token_to_complete -eq 0 ]; then\n        printf %s\\\\n \\\n            add \\\n            apply \\\n            blame \\\n            blame-jump \\\n            checkout \\\n            commit \\\n            diff \\\n            edit \\\n            grep \\\n            hide-diff \\\n            init \\\n            log \\\n            next-hunk \\\n            prev-hunk \\\n            reset \\\n            rm \\\n            show \\\n            show-branch \\\n            show-diff \\\n            status \\\n            update-diff \\\n        ;\n    else\n        # Quote POSIX extended regular expressions, see SPEC_CHAR/QUOTED_CHAR in\n        # https://pubs.opengroup.org/onlinepubs/009696899/basedefs/xbd_chap09.html#tag_09_05_01\n        sed_quote_ext_re() {\n            # SED uses BREs by default, and matching in a group the ERE SPEC_CHAR\n            sed -e 's/[\\^\\.\\[\\$\\\\()|\\*+?{]/\\\\&/g' \"$@\"\n            # to balance kakoune %<lbrace>: }\n        }\n        suggest_refs() {\n            # Ten most recent refs\n            git for-each-ref --format='%(refname:short)' --sort=-comitterdate --count=10\n            # Commits, using name to match instead of hash (in the absence of having subject\n            # as a comment for completion).\n            git log -100 --format='%s' | sed_quote_ext_re -e 's|.*|@^{/&}|g'\n        }\n        case \"$1\" in\n            commit) printf -- \"--amend\\n--no-edit\\n--all\\n--reset-author\\n--fixup\\n--squash\\n\"; git ls-files -m ;;\n            add) git ls-files -dmo --exclude-standard ;;\n            apply) printf -- \"--reverse\\n--cached\\n--index\\n--3way\\n\" ;;\n            grep|edit) git ls-files -c --recurse-submodules ;;\n            diff) printf -- \"--cached\\n----merge-base\\n--stat\\n--word-diff\\n\"; suggest_refs ;;\n            show) printf -- \"--patch\\n--stat\\n--remerge-diff\\n\"; suggest_refs ;;\n            log) printf -- \"--patch\\n--stat\\n--remerge-diff\\n--walk-reflogs\\n--grep\\n\"; suggest_refs ;;\n        esac\n    fi\n  } \\\n  git %{ evaluate-commands %sh{\n    cd_bufdir() {\n        dirname_buffer=\"${kak_buffile%/*}\"\n        if [ \"${dirname_buffer}\" = \"${kak_buffile}\" ]; then\n            printf 'fail git: cannot operate on scratch buffer: %s\\n' \"${kak_buffile}\"\n            return 1\n        fi\n        cd \"${dirname_buffer}\" 2>/dev/null || {\n            printf 'fail git: unable to change the current working directory to: %s\\n' \"${dirname_buffer}\"\n            return 1\n        }\n    }\n    kakquote() {\n        printf \"%s\" \"$1\" | sed \"s/'/''/g; 1s/^/'/; \\$s/\\$/'/\"\n    }\n\n    show_git_cmd_output() {\n        local filetype\n\n        case \"$1\" in\n           diff) filetype=git-diff ;;\n           show) filetype=git-log ;;\n           show-branch) filetype=git-show-branch ;;\n           log)  filetype=git-log ;;\n           status)  filetype=git-status ;;\n           *) return 1 ;;\n        esac\n        output=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-git.XXXXXXXX)/fifo\n        mkfifo ${output}\n        ( trap - INT QUIT; git \"$@\" > ${output} 2>&1 & ) > /dev/null 2>&1 < /dev/null\n\n        printf %s \"evaluate-commands -try-client '$kak_opt_docsclient' '\n                  edit! -fifo ${output} *git*\n                  set-option buffer filetype ${filetype}\n                  $(hide_blame)\n                  set-option buffer git_blob %{}\n                  hook -always -once buffer BufCloseFifo .* ''\n                      nop %sh{ rm -r $(dirname ${output}) }\n                      $(printf %s \"${on_close_fifo}\" | sed \"s/'/''''/g\")\n                  ''\n        '\"\n    }\n\n    hide_blame() {\n        printf %s \"\n            set-option buffer git_blame_flags $kak_timestamp\n            set-option buffer git_blame_index $kak_timestamp\n            set-option buffer git_blame %{}\n            try %{ remove-highlighter window/git-blame }\n            unmap window normal <ret> %{:git blame-jump<ret>}\n        \"\n    }\n\n    diff_buffer_against_rev() {\n        if ! command -v diff >/dev/null; then\n            echo >${kak_command_fifo} \"fail diff: command not found\"\n        fi\n        rev=$1 # empty means index\n        shift\n        buffile_relative=${kak_buffile#\"$(git rev-parse --show-toplevel)/\"}\n        echo >${kak_command_fifo} \"evaluate-commands -no-hooks write ${kak_response_fifo}\"\n        git show \"$rev:${buffile_relative}\" |\n            diff - ${kak_response_fifo} \"$@\" |\n            awk -v buffile_relative=\"$buffile_relative\" '\n                NR == 1 { print \"--- a/\" buffile_relative }\n                NR == 2 { print \"+++ b/\" buffile_relative }\n                NR > 2\n            '\n    }\n\n    blame_toggle() {\n        echo >${kak_command_fifo} \"try %{\n            add-highlighter window/git-blame flag-lines Information git_blame_flags\n            echo -to-file ${kak_response_fifo}\n        } catch %{\n            echo -to-file ${kak_response_fifo} 'hide_blame; exit'\n        }\"\n        eval $(cat ${kak_response_fifo})\n        if [ -z \"${kak_opt_git_blob}\" ] && {\n            [ \"${kak_opt_filetype}\" = git-diff ] || [ \"${kak_opt_filetype}\" = git-log ]\n        } then {\n            echo 'try %{ remove-highlighter window/git-blame }'\n            printf >${kak_command_fifo} %s '\n                evaluate-commands -client '${kak_client}' -draft %{\n                    try %{\n                        execute-keys <a-l><semicolon><a-?>^commit<ret><a-semicolon>\n                    } catch %{\n                        # Missing commit line, assume it is an uncommitted change.\n                        execute-keys <a-l><semicolon>Gg<a-semicolon>\n                    }\n                    require-module diff\n                    try %{\n                        diff-parse BEGIN %{\n                            $directory = qx(git rev-parse --show-toplevel);\n                            chomp $directory;\n                        } END %{\n                            my $filename = $other_file;\n                            my $line = $other_file_line;\n                            if (not defined $commit) {\n                                $commit = \"HEAD\";\n                                if ($diff_line_text =~ m{^\\+}) {\n                                    print \"echo -to-file '${kak_response_fifo}' -quoting shell \"\n                                        . \"%{git blame: blame from HEAD does not work on added lines}\";\n                                    exit;\n                                }\n                            } elsif ($diff_line_text =~ m{^[-]}) {\n                                $commit = \"$commit~\";\n                            } else {\n                                $filename = $file;\n                                $line = $file_line;\n                            }\n                            $line = $line or 1;\n                            my $filename_relative = substr($filename, length \"$directory/\");\n                            printf \"echo -to-file '${kak_response_fifo}' -quoting shell %s %s %s %d %d\",\n                               $commit, quote($filename), quote($filename_relative),\n                               $line, ('${kak_cursor_column}' - 1);\n                        }\n                    } catch %{\n                        echo -to-file '${kak_response_fifo}' -quoting shell -- %val{error}\n                    }\n                }\n            '\n            n=$#\n            eval set -- \"$(cat ${kak_response_fifo})\" \"$@\"\n            if [ $# -eq $((n+1)) ]; then\n                echo fail -- \"$(kakquote \"$1\")\"\n                exit\n            fi\n            commit=$1\n            file_absolute=$2\n            file_relative=$3\n            cursor_line=$4\n            cursor_column=$5\n            shift 5\n            # Log commit and file name because they are only echoed briefly\n            # and not shown elsewhere (we don't have a :messages buffer).\n            message=\"Blaming $file_relative as of $(git rev-parse --short $commit)\"\n            echo \"echo -debug -- $(kakquote \"$message\")\"\n            on_close_fifo=\"\n                execute-keys -client ${kak_client} ${cursor_line}g<a-h>${cursor_column}lh\n                evaluate-commands -client ${kak_client} %{\n                    set-option buffer git_blob $(kakquote \"$commit:$file_absolute\")\n                    git blame $(for arg; do kakquote \"$arg\"; printf \" \"; done)\n                    echo -markup -- $(kakquote \"{Information}{\\\\}$message. Press <ret> to jump to blamed commit\")\n                    hook -once window NormalIdle .* %{ execute-keys vv }\n                }\n            \" show_git_cmd_output show \"$commit:$file_relative\"\n            exit\n        } fi\n        if [ -n \"${kak_opt_git_blob}\" ]; then {\n            set -- \"$@\" \"${kak_opt_git_blob%%:*}\" -- \"${kak_opt_git_blob#*:}\"\n            blame_stdin=/dev/null\n        } else {\n            if ! error=$(cd_bufdir); then\n                echo 'remove-highlighter window/git-blame'\n                printf %s\\\\n \"$error\"\n                exit\n            fi\n            set -- \"$@\" --contents - -- \"${kak_buffile}\" # use stdin to work around git bug\n            blame_stdin=$(mktemp \"${TMPDIR:-/tmp}\"/kak-git.XXXXXX)\n            echo >${kak_command_fifo} \"\n                evaluate-commands -no-hooks write -force ${blame_stdin}\n                echo -to-file ${kak_response_fifo}\n            \"\n            : <${kak_response_fifo}\n        } fi\n        echo 'map window normal <ret> %{:git blame-jump<ret>}'\n        echo 'echo -markup {Information}Press <ret> to jump to blamed commit'\n        (\n            trap - INT QUIT\n            printf %s \"evaluate-commands -client '$kak_client' %{\n                      set-option buffer=$kak_bufname git_blame_flags '$kak_timestamp'\n                      set-option buffer=$kak_bufname git_blame_index '$kak_timestamp'\n                      set-option buffer=$kak_bufname git_blame ''\n                  }\" | kak -p ${kak_session}\n            if ! stderr=$({ git blame --incremental \"$@\" <${blame_stdin} | perl -wne '\n                  BEGIN {\n                  use POSIX qw(strftime);\n                  sub quote {\n                      my $SQ = \"'\\''\";\n                      my $token = shift;\n                      $token =~ s/$SQ/$SQ$SQ/g;\n                      return \"$SQ$token$SQ\";\n                  }\n                  sub send_flags {\n                      my $is_last_call = shift;\n                      if (not defined $line) {\n                          if ($is_last_call) { exit 1; }\n                          return;\n                      }\n                      my $text = substr($sha,0,7) . \" \" . $dates{$sha} . \" \" . $authors{$sha};\n                      $text =~ s/~/~~/g;\n                      for ( my $i = 0; $i < $count; $i++ ) {\n                          $flags .= \" %~\" . ($line+$i) . \"|$text~\";\n                      }\n                      $now = time();\n                      # Send roughly one update per second, to avoid creating too many kak processes.\n                      if (!$is_last_call && defined $last_sent && $now - $last_sent < 1) {\n                          return\n                      }\n                      open CMD, \"|-\", \"kak -p $ENV{kak_session}\";\n                      print CMD \"set-option -add buffer=$ENV{kak_bufname} git_blame_flags $flags;\";\n                      print CMD \"set-option -add buffer=$ENV{kak_bufname} git_blame_index $index;\";\n                      print CMD \"set-option -add buffer=$ENV{kak_bufname} git_blame \" . quote $raw_blame;\n                      close(CMD);\n                      $flags = \"\";\n                      $index = \"\";\n                      $raw_blame = \"\";\n                      $last_sent = $now;\n                  }\n                  }\n                  $raw_blame .= $_;\n                  chomp;\n                  if (m/^([0-9a-f]+) ([0-9]+) ([0-9]+) ([0-9]+)/) {\n                      send_flags(0);\n                      $sha = $1;\n                      $line = $3;\n                      $count = $4;\n                      for ( my $i = 0; $i < $count; $i++ ) {\n                          $index .= \" \" . ($line+$i) . \"|$.,$i\";\n                      }\n                  }\n                  if (m/^author /) {\n                      $authors{$sha} = substr($_,7);\n                      $authors{$sha} = \"Not Committed Yet\" if $authors{$sha} eq \"External file (--contents)\";\n                  }\n                  if (m/^author-time ([0-9]*)/) { $author_time = $1; }\n                  if (m/^author-tz ([+-])(\\d\\d)/) {\n                      my $sign = $1 eq \"+\" ? \"-\" : \"+\";\n                      local $ENV{\"TZ\"} = \"UTC${sign}$2\";\n                      $dates{$sha} = strftime(\"%F %T\", localtime $author_time);\n                  }\n                  END { send_flags(1); }'\n            } 2>&1); then\n                escape2() { printf %s \"$*\" | sed \"s/'/''''/g\"; }\n                echo \"evaluate-commands -client ${kak_client} '\n                    evaluate-commands -draft %{\n                        buffer %{${kak_buffile}}\n                        git hide-blame\n                    }\n                    echo -debug failed to run git blame\n                    echo -debug git stderr: <<<\n                    echo -debug ''$(escape2 \"$stderr\")>>>''\n                    echo -markup %{{Error}failed to run git blame, see *debug* buffer}\n                '\" | kak -p ${kak_session}\n            fi\n            if [ ${blame_stdin} != /dev/null ]; then\n                rm ${blame_stdin}\n            fi\n        ) > /dev/null 2>&1 < /dev/null &\n    }\n\n    run_git_cmd() {\n        if git \"${@}\" > /dev/null 2>&1; then\n          printf %s \"echo -markup '{Information}git $1 succeeded'\"\n        else\n          printf 'fail git %s failed\\n' \"$1\"\n        fi\n    }\n\n    update_diff() {\n        (\n            cd_bufdir || exit\n            diff_buffer_against_rev \"\" -U0 | perl -e '\n            use utf8;\n            $flags = $ENV{\"kak_timestamp\"};\n            $add_char = $ENV{\"kak_opt_git_diff_add_char\"};\n            $del_char = $ENV{\"kak_opt_git_diff_del_char\"};\n            $top_char = $ENV{\"kak_opt_git_diff_top_char\"};\n            $mod_char = $ENV{\"kak_opt_git_diff_mod_char\"};\n            foreach $line (<STDIN>) {\n                if ($line =~ /@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))?/) {\n                    $from_line = $1;\n                    $from_count = ($2 eq \"\" ? 1 : $2);\n                    $to_line = $3;\n                    $to_count = ($4 eq \"\" ? 1 : $4);\n\n                    if ($from_count == 0 and $to_count > 0) {\n                        for $i (0..$to_count - 1) {\n                            $line = $to_line + $i;\n                            $flags .= \" $line|\\{green\\}$add_char\";\n                        }\n                    }\n                    elsif ($from_count > 0 and $to_count == 0) {\n                        if ($to_line == 0) {\n                            $flags .= \" 1|\\{red\\}$top_char\";\n                        } else {\n                            $flags .= \" $to_line|\\{red\\}$del_char\";\n                        }\n                    }\n                    elsif ($from_count > 0 and $from_count == $to_count) {\n                        for $i (0..$to_count - 1) {\n                            $line = $to_line + $i;\n                            $flags .= \" $line|\\{blue\\}$mod_char\";\n                        }\n                    }\n                    elsif ($from_count > 0 and $from_count < $to_count) {\n                        for $i (0..$from_count - 1) {\n                            $line = $to_line + $i;\n                            $flags .= \" $line|\\{blue\\}$mod_char\";\n                        }\n                        for $i ($from_count..$to_count - 1) {\n                            $line = $to_line + $i;\n                            $flags .= \" $line|\\{green\\}$add_char\";\n                        }\n                    }\n                    elsif ($to_count > 0 and $from_count > $to_count) {\n                        for $i (0..$to_count - 2) {\n                            $line = $to_line + $i;\n                            $flags .= \" $line|\\{blue\\}$mod_char\";\n                        }\n                        $last = $to_line + $to_count - 1;\n                        $flags .= \" $last|\\{blue+u\\}$mod_char\";\n                    }\n                }\n            }\n            print \"set-option buffer git_diff_flags $flags\\n\"\n        ' )\n    }\n\n    jump_hunk() {\n        direction=$1\n        set -- ${kak_opt_git_diff_flags}\n        shift\n\n        if [ $# -lt 1 ]; then\n            echo \"fail 'no git hunks found, try \\\":git show-diff\\\" first'\"\n            exit\n        fi\n\n        # Update hunk list if required\n        if [ \"$kak_timestamp\" != \"${kak_opt_git_hunk_list%% *}\" ]; then\n            hunks=$kak_timestamp\n\n            prev_line=\"-1\"\n            for line in \"$@\"; do\n                line=\"${line%%|*}\"\n                if [ \"$((line - prev_line))\" -gt 1 ]; then\n                    hunks=\"$hunks $line\"\n                fi\n                prev_line=\"$line\"\n            done\n            echo \"set-option buffer git_hunk_list $hunks\"\n            hunks=${hunks#* }\n        else\n            hunks=${kak_opt_git_hunk_list#* }\n        fi\n\n        prev_hunk=\"\"\n        next_hunk=\"\"\n        for hunk in ${hunks}; do\n            if   [ \"$hunk\" -lt \"$kak_cursor_line\" ]; then\n                prev_hunk=$hunk\n            elif [ \"$hunk\" -gt \"$kak_cursor_line\" ]; then\n                next_hunk=$hunk\n                break\n            fi\n        done\n\n        wrapped=false\n        if [ \"$direction\" = \"next\" ]; then\n            if [ -z \"$next_hunk\" ]; then\n                next_hunk=${hunks%% *}\n                wrapped=true\n            fi\n            if [ -n \"$next_hunk\" ]; then\n                echo \"select $next_hunk.1,$next_hunk.1\"\n            fi\n        elif [ \"$direction\" = \"prev\" ]; then\n            if [ -z \"$prev_hunk\" ]; then\n                wrapped=true\n                prev_hunk=${hunks##* }\n            fi\n            if [ -n \"$prev_hunk\" ]; then\n                echo \"select $prev_hunk.1,$prev_hunk.1\"\n            fi\n        fi\n\n        if [ \"$wrapped\" = true ]; then\n            echo \"echo -markup '{Information}git hunk search wrapped around buffer'\"\n        fi\n    }\n\n    commit() {\n        # Handle case where message needs not to be edited\n        if grep -E -q -e \"-m|-F|-C|--message=.*|--file=.*|--reuse-message=.*|--no-edit|--fixup.*|--squash.*\"; then\n            if git commit \"$@\" > /dev/null 2>&1; then\n                echo 'echo -markup \"{Information}Commit succeeded\"'\n            else\n                echo 'fail Commit failed'\n            fi\n            exit\n        fi <<-EOF\n\t\t\t$@\n\t\tEOF\n\n        # fails, and generate COMMIT_EDITMSG\n        GIT_EDITOR='' EDITOR='' git commit \"$@\" > /dev/null 2>&1\n        msgfile=\"$(git rev-parse --git-dir)/COMMIT_EDITMSG\"\n        printf %s \"edit '$msgfile'\n              hook buffer BufWritePost '.*\\Q$msgfile\\E' %{ evaluate-commands %sh{\n                  if git commit -F '$msgfile' --cleanup=strip $* > /dev/null; then\n                     printf %s 'evaluate-commands -client $kak_client echo -markup %{{Information}Commit succeeded}; delete-buffer'\n                  else\n                     printf 'evaluate-commands -client %s fail Commit failed\\n' \"$kak_client\"\n                  fi\n              } }\"\n    }\n\n    blame_jump() {\n        if [ -z \"${kak_client}\" ]; then\n            echo fail git blame-jump: no client in context\n            exit\n        fi\n        echo >${kak_command_fifo} \"echo -to-file ${kak_response_fifo} -- %opt{git_blame}\"\n        blame_info=$(cat < ${kak_response_fifo})\n        blame_index=\n        cursor_column=${kak_cursor_column}\n        cursor_line=${kak_cursor_line}\n        if [ -n \"$blame_info\" ]; then {\n            echo >${kak_command_fifo} \"\n                update-option buffer git_blame_index\n                echo -to-file ${kak_response_fifo} -- %opt{git_blame_index}\n            \"\n            blame_index=$(cat < ${kak_response_fifo})\n        } elif [ \"${kak_opt_filetype}\" = git-diff ] || [ \"${kak_opt_filetype}\" = git-log ]; then {\n            printf >${kak_command_fifo} %s '\n                evaluate-commands -draft %{\n                    try %{\n                        execute-keys <a-l><semicolon><a-?>^commit<ret><a-semicolon>\n                    } catch %{\n                        # Missing commit line, assume it is an uncommitted change.\n                        execute-keys <a-l><semicolon><a-?>\\A<ret><a-semicolon>\n                    }\n                    require-module diff\n                    try %{\n                        diff-parse BEGIN %{\n                            $version = \"-\";\n                            $directory = qx(git rev-parse --show-toplevel);\n                            chomp $directory;\n                        } END %{\n                            if ($diff_line_text !~ m{^[ -]}) {\n                                print quote \"git blame-jump: recursive blame only works on context or deleted lines\";\n                                exit 1;\n                            }\n                            if (not defined $commit) {\n                                $commit = \"HEAD\";\n                            } else {\n                                $commit = \"$commit~\";\n                            }\n                            printf \"echo -to-file '${kak_response_fifo}' -quoting shell %s %s %d %d\",\n                                    $commit, quote($file), $file_line, ('$cursor_column' - 1);\n                        }\n                    } catch %{\n                        echo -to-file '${kak_response_fifo}' -quoting shell -- %val{error}\n                    }\n                }\n            '\n            eval set -- \"$(cat ${kak_response_fifo})\"\n            if [ $# -eq 1 ]; then\n                echo fail -- \"$(kakquote \"$1\")\"\n                exit\n            fi\n            starting_commit=$1\n            file=$2\n            cursor_line=$3\n            cursor_column=$4\n            blame_info=$(git blame --porcelain \"$starting_commit\" -L\"$cursor_line,$cursor_line\" -- \"$file\")\n            if [ $? -ne 0 ]; then\n                echo 'echo -markup %{{Error}failed to run git blame, see *debug* buffer}'\n                exit\n            fi\n        } else {\n            if [ -n \"${kak_opt_git_blob}\" ]; then {\n                set -- \"${kak_opt_git_blob%%:*}\" -- \"${kak_opt_git_blob#*:}\"\n                blame_stdin=/dev/null\n            } else {\n                set -- --contents - -- \"${kak_buffile}\" # use stdin to work around git bug\n                blame_stdin=${kak_response_fifo}\n                echo >${kak_command_fifo} \"evaluate-commands -no-hooks write ${kak_response_fifo}\"\n            } fi\n            if ! blame_info=$(\n                git blame --porcelain -L\"$cursor_line,$cursor_line\" \"$@\" <${blame_stdin})\n            then\n                echo 'echo -markup %{{Error}failed to run git blame, see *debug* buffer}'\n                exit\n            fi\n        } fi\n        eval \"$(printf '%s\\n---\\n%s' \"$blame_index\" \"$blame_info\" |\n                client=${kak_opt_docsclient:-$kak_client} \\\n                cursor_line=$cursor_line cursor_column=$cursor_column \\\n                perl -wne '\n            BEGIN {\n                use POSIX qw(strftime);\n                our $SQ = \"'\\''\";\n                sub escape {\n                   return shift =~ s/$SQ/$SQ$SQ/gr\n                }\n                sub quote {\n                    my $token = escape shift;\n                    return \"$SQ$token$SQ\";\n                }\n                sub shellquote {\n                    my $token = shift;\n                    $token =~ s/$SQ/$SQ\\\\$SQ$SQ/g;\n                    return \"$SQ$token$SQ\";\n                }\n                sub perlquote {\n                    my $token = shift;\n                    $token =~ s/\\\\/\\\\\\\\/g;\n                    $token =~ s/$SQ/\\\\$SQ/g;\n                    return \"$SQ$token$SQ\";\n                }\n                $target = $ENV{\"cursor_line\"};\n                $state = \"index\";\n            }\n            chomp;\n            if ($state eq \"index\") {\n                if ($_ eq \"---\") {\n                    $state = \"blame\";\n                    next;\n                }\n                @blame_index = split;\n                next unless @blame_index;\n                shift @blame_index;\n                foreach (@blame_index) {\n                    $_ =~ m{(\\d+)\\|(\\d+),(\\d+)} or die \"bad blame index flag: $_\";\n                    my $buffer_line = $1;\n                    if ($buffer_line == $target) {\n                        $target_in_blame = $2;\n                        $target_offset = $3;\n                        last;\n                    }\n                }\n                defined $target_in_blame and next, or last;\n            }\n            if (m/^([0-9a-f]+) ([0-9]+) ([0-9]+) ([0-9]+)/) {\n                if ($done) {\n                    last;\n                }\n                $sha = $1;\n                $old_line = $2;\n                $new_line = $3;\n                $count = $4;\n                if (defined $target_in_blame) {\n                    if ($target_in_blame == $. - 2) {\n                        $old_line += $target_offset;\n                        $done = 1;\n                    }\n                } else {\n                    if ($new_line <= $target and $target < $new_line + $count) {\n                        $old_line += $target - $new_line;\n                        $done = 1;\n                    }\n                }\n            }\n            if (m/^filename /) { $old_filenames{$sha} = substr($_,9) }\n            if (m/^author /) { $authors{$sha} = substr($_,7) }\n            if (m/^author-time ([0-9]*)/) { $author_time = $1; }\n            if (m/^author-tz ([+-])(\\d\\d)/) {\n                my $sign = $1 eq \"+\" ? \"-\" : \"+\";\n                local $ENV{\"TZ\"} = \"UTC${sign}$2\";\n                $dates{$sha} = strftime(\"%F\", localtime $author_time);\n            }\n            if (m/^summary /) { $summaries{$sha} = substr($_,8) }\n            END {\n                if (@blame_index and not defined $target_in_blame) {\n                    print \"echo fail git blame-jump: line has no blame information;\";\n                    exit;\n                }\n                if (not defined $sha) {\n                    print \"echo fail git blame-jump: missing blame info\";\n                    exit;\n                }\n                if (not $done) {\n                    print \"echo \\\"fail git blame-jump: line not found in annotations (blame still loading?)\\\"\";\n                    exit;\n                }\n                $info = \"{Information}{\\\\}\";\n                if ($sha =~ m{^0+$}) {\n                    $old_filename = $ENV{\"kak_buffile\"};\n                    $old_filename = substr $old_filename, length($ENV{\"PWD\"}) + 1;\n                    $show_diff = \"diff HEAD\";\n                    $info .= \"Not committed yet\";\n                } else {\n                    $old_filename = $old_filenames{$sha};\n                    $author = $authors{$sha};\n                    $date = $dates{$sha};\n                    $summary = $summaries{$sha};\n                    $show_diff = \"show $sha\";\n                    $info .= \"$date $author \\\"$summary\\\"\";\n                }\n                $on_close_fifo = \"\n                    evaluate-commands -draft $SQ\n                        execute-keys <percent>\n                        require-module diff\n                        diff-parse BEGIN %{\n                            \\$in_file = \" . escape(perlquote($old_filename)) . \";\n                            \\$in_file_line = $old_line;\n                        } END $SQ$SQ\n                            print \\\"execute-keys -client $ENV{client} \\${diff_line}g<a-h>$ENV{cursor_column}l;\\\";\n                            printf \\\"evaluate-commands -client $ENV{client} $SQ$SQ$SQ$SQ\n                                hook -once window NormalIdle .* $SQ$SQ$SQ$SQ$SQ$SQ$SQ$SQ\n                                    execute-keys vv\n                                    echo -markup -- %s\n                                $SQ$SQ$SQ$SQ$SQ$SQ$SQ$SQ\n                            $SQ$SQ$SQ$SQ ;\\\",\" . escape(escape(perlquote(escape(escape(quote($info)))))) . \";\n                        $SQ$SQ\n                    $SQ\n                \";\n                printf \"on_close_fifo=%s show_git_cmd_output %s\",\n                    shellquote($on_close_fifo), $show_diff;\n            }\n        ')\"\n    }\n\n    apply_selections() {\n        if [ -z \"$(cd_bufdir >/dev/null 2>&1 && git ls-files -- \":(literal)${kak_buffile}\")\" ]; then {\n            enquoted=\"$(printf '\"%s\" ' \"$@\")\"\n            echo \"require-module patch\"\n            echo \"patch git apply $enquoted\"\n            return\n        } fi\n        base_rev=HEAD\n        index_only=false\n        index=false\n        reverse=false\n        for arg; do\n            case \"$arg\" in\n                (--cached) index_only=true ; base_rev= ;;\n                (--index) index=true ;;\n                (--reverse|-R) reverse=true ;;\n            esac\n        done\n        if ! $reverse && ! $index_only; then\n            echo \"fail %{git apply on buffer contents doesn't make sense without --reverse or --cached}\"\n            exit\n        fi\n        cd_bufdir || exit\n        num_inserted=0\n        num_deleted=0\n        for selection_desc in $kak_selections_desc; do {\n            IFS=' .,' read anchor_line _ cursor_line _ <<-EOF\n                $selection_desc\n\t\tEOF\n            if [ $anchor_line -lt $cursor_line ]; then\n                min_line=$anchor_line\n                max_line=$cursor_line\n            else\n                min_line=$cursor_line\n                max_line=$anchor_line\n            fi\n            intended_diff='diff_buffer_against_rev \"$base_rev\" -u'\n            if $index; then {\n                git update-index --refresh \"${kak_buffile}\" >/dev/null\n                intended_diff='git diff --no-ext-diff HEAD -- \":(literal)${kak_buffile}\"'\n            } elif $index_only && $reverse; then {\n                diff=$(eval \"$intended_diff\")\n                if [ -n \"$diff\" ]; then {\n                    # Convert from buffile lines to index lines.\n                    for line in min_line max_line; do {\n                        if ! index_line_or_error_message=$(\n                            eval file_line=\\$$line\n                            printf %s \"$diff\" |\n                                perl \"${kak_runtime}/rc/filetype/diff-parse.pl\" \\\n                                    BEGIN '\n                                        $in_file = \"\"; # no need to check filename, there is only one\n                                        $in_file_line = '\"$file_line\"';\n                                    ' END '\n                                        $other_file_line++ if $diff_line_text =~ m{^\\+};\n                                        $other_file_line += $in_file_line - $file_line;\n                                        print \"$other_file_line\\n\";\n                                    '\n                        ); then\n                            echo fail \"git apply: $index_line_or_error_message\"\n                            exit\n                        fi\n                        eval $line=$index_line_or_error_message\n                    } done\n                } fi\n                intended_diff='git diff --no-ext-diff --cached -- \":(literal)${kak_buffile}\"'\n            } fi\n            diff=$(eval \"$intended_diff\" |\n                    perl \"${kak_runtime}\"/rc/tools/patch-range.pl -line-numbers-from-new-file \\\n                        $min_line $max_line sh -c cat -- \"$@\" # forward any --reverse arg\n                   printf .) # avoid stripping newline\n            diff=${diff%.}\n            if ! printf %s \"$diff\" | git apply \"$@\"; then\n                printf >&2 \"git apply: error running:\\n\\$ git apply %s << EOF\\n\" \"$*\"\n                printf >&2 %s \"$diff\"\n                printf >&2 'EOF\\n'\n                echo \"fail 'git apply: failed to apply selections, see *debug* buffer'\"\n                exit\n            fi\n            count() {\n                printf %s \"$diff\" | awk '\n                    BEGIN { n = 0 }\n                    /^@@/,/^$/ { if ($0 ~ /^'\"$1\"'/) { n++ } }\n                    END { print n }'\n            }\n            num_inserted=$(( $num_inserted + $(count +) ))\n            num_deleted=$(( $num_deleted + $(count -) ))\n        } done\n        if ! $index_only && ! $kak_modified; then\n            echo edit!\n            echo git update-diff\n        else\n            update_diff\n        fi\n        msg=\n        case $index_only,$reverse,$index in\n            (true,false,*) msg=Staged ;;\n            (true,true,*) msg=Unstaged ;;\n            (false,true,false) msg=Reverted ;;\n            (false,true,true) msg='Unstaged and reverted' ;;\n        esac\n        case $num_inserted,$num_deleted in\n            (*,0) msg=\"$msg $num_inserted inserted line(s)\";;\n            (0,*) msg=\"$msg $num_deleted deleted line(s)\";;\n            (*,*) msg=\"$msg $num_inserted inserted and $num_deleted deleted lines\";;\n        esac\n        echo \"echo -markup '{Information}{\\\\}$msg'\"\n    }\n\n    case \"$1\" in\n        apply)\n            shift\n            apply_selections \"$@\"\n            ;;\n        show|show-branch|log|diff|status)\n            show_git_cmd_output \"$@\"\n            ;;\n        blame)\n            shift\n            blame_toggle \"$@\"\n            ;;\n        blame-jump)\n            blame_jump\n            ;;\n        hide-blame)\n            hide_blame\n            ;;\n        show-diff)\n            echo 'try %{ add-highlighter window/git-diff flag-lines Default git_diff_flags }'\n            update_diff\n            ;;\n        hide-diff)\n            echo 'try %{ remove-highlighter window/git-diff }'\n            ;;\n        update-diff) update_diff ;;\n        next-hunk) jump_hunk next ;;\n        prev-hunk) jump_hunk prev ;;\n        commit)\n            shift\n            commit \"$@\"\n            ;;\n        init)\n            shift\n            git init \"$@\" > /dev/null 2>&1\n            ;;\n        add|rm)\n            cmd=\"$1\"\n            shift\n            run_git_cmd $cmd \"${@:-\"${kak_buffile}\"}\"\n            ;;\n        reset|checkout)\n            run_git_cmd \"$@\"\n            ;;\n        grep)\n            shift\n            enquoted=\"$(printf '\"%s\" ' \"$@\")\"\n            printf %s \"try %{\n                set-option current grepcmd 'git grep -n --column'\n                grep $enquoted\n                set-option current grepcmd '$kak_opt_grepcmd'\n            }\"\n            ;;\n        edit)\n            shift\n            enquoted=\"$(printf '\"%s\" ' \"$@\")\"\n            printf %s \"edit -existing -- $enquoted\"\n            ;;\n        *)\n            printf \"fail unknown git command '%s'\\n\" \"$1\"\n            exit\n            ;;\n    esac\n}}\n\n# Works within :git diff and :git show\ndefine-command git-diff-goto-source \\\n    -docstring 'Navigate to source by pressing the enter key in hunks when git diff is displayed. Works within :git diff and :git show' %{\n    require-module diff\n    diff-jump %sh{ git rev-parse --show-toplevel }\n}\n"
  },
  {
    "path": "rc/tools/go/gopls.kak",
    "content": "# gopls.kak: gopls bindings for kakoune\n\ndefine-command -params 1 -docstring %{\ngopls <command>: gopls command wrapper\n\nAll commands are forwarded to gopls utility\nAvailable commands are:\n    format\n    imports\n    definition\n    references\n} -shell-script-candidates %{\n    printf \"format\\nimports\\ndefinition\\nreferences\\n\"\n} \\\ngopls %{\n    require-module gopls\n    evaluate-commands %sh{\n        case \"$1\" in\n        format|imports)\n            printf %s\\\\n \"gopls-cmd $1\"\n            ;;\n        definition)\n            printf %s\\\\n \"gopls-def\"\n            ;;\n        references)\n            printf %s\\\\n \"gopls-ref\"\n            ;;\n        *)\n            printf \"fail Unknown gopls command '%s'\\n\" \"$1\"\n            exit\n            ;;\n        esac\n    }\n}\n\nprovide-module gopls %§\n\nevaluate-commands %sh{\n    if ! command -v gopls > /dev/null 2>&1; then\n        echo \"fail Please install gopls or add to PATH!\"\n    fi\n}\n\n# Temp dir preparation\ndeclare-option -hidden str gopls_tmp_dir\ndefine-command -hidden -params 0 gopls-prepare %{\n    evaluate-commands %sh{\n        dir=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-gopls.XXXXXXXX)\n        printf %s\\\\n \"set-option buffer gopls_tmp_dir ${dir}\"\n    }\n}\n\n# gopls format/imports\ndefine-command -hidden -params 1 gopls-cmd %{\n    gopls-prepare\n    evaluate-commands %sh{\n        dir=${kak_opt_gopls_tmp_dir}\n        gopls \"$1\" -w \"${kak_buffile}\" 2> \"${dir}/stderr\"\n        if [ $? -ne 0 ]; then\n            # show error messages in *debug* buffer\n            printf %s\\\\n \"echo -debug %file{${dir}/stderr}\"\n        fi\n    }\n    edit!\n    nop %sh{ rm -rf \"${kak_opt_gopls_tmp_dir}\" }\n}\n\n# gopls definition\ndefine-command -hidden -params 0 gopls-def %{\n    evaluate-commands %sh{\n        jump=$( gopls definition \"${kak_buffile}:${kak_cursor_line}:${kak_cursor_column}\" 2> /dev/null \\\n            |sed -e 's/-[0-9]\\+:.*//; s/:/ /g; q' )\n        if [ -n \"${jump}\" ]; then\n            printf %s\\\\n \"evaluate-commands -try-client '${kak_opt_jumpclient}' %{\n                edit ${jump}\n            }\"\n        fi\n    }\n}\n\n# gopls references\ndefine-command -hidden -params 0 gopls-ref %{\n    gopls-prepare\n    evaluate-commands %sh{\n        dir=${kak_opt_gopls_tmp_dir}\n        mkfifo \"${dir}/fifo\"\n        ( { trap - INT QUIT; gopls references \"${kak_buffile}:${kak_cursor_line}:${kak_cursor_column}\"\n          } > \"${dir}/fifo\" 2> /dev/null & ) > /dev/null 2>&1 < /dev/null\n        # using filetype=grep for nice hilight and <ret> mapping\n        printf %s\\\\n \"evaluate-commands -try-client '${kak_opt_toolsclient}' %{\n            edit! -fifo '${dir}/fifo' *gopls-refs*\n            set-option buffer filetype grep\n            hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r '${dir}' } }\n        }\"\n    }\n}\n\n§\n"
  },
  {
    "path": "rc/tools/grep.kak",
    "content": "declare-option -docstring \"shell command run to search for subtext in a file/directory\" \\\n    str grepcmd 'grep -RHn'\n\nprovide-module grep %{\n\nrequire-module fifo\nrequire-module jump\n\ndefine-command -params .. -docstring %{\n    grep [<arguments>]: grep utility wrapper\n    All optional arguments are forwarded to the grep utility\n    Passing no argument will perform a literal-string grep for the current selection\n} grep %{\n    evaluate-commands -save-regs gs %{\n        set-register g %opt{grepcmd}\n        set-register s %val{selection}\n        evaluate-commands -try-client %opt{toolsclient} %{\n            fifo -name *grep* -script %{\n                trap - INT QUIT\n                grepcmd=${kak_reg_g}\n                selection=${kak_reg_s}\n                if [ $# -eq 0 ]; then\n                    case \"$grepcmd\" in\n                    ag\\ * | git\\ grep\\ * | grep\\ * | rg\\ * | ripgrep\\ * | ugrep\\ * | ug\\ *)\n                        set -- -F -- \"$selection\"\n                        ;;\n                    ack\\ *)\n                        set -- -Q -- \"$selection\"\n                        ;;\n                    *)\n                        set -- -- \"$selection\"\n                        ;;\n                    esac\n                fi\n                eval \"$grepcmd \\\"\\$@\\\"\" 2>&1 | tr -d '\\r'\n            } -- %arg{@}\n            set-option buffer filetype grep\n            set-option buffer jump_current_line 0\n        }\n    }\n}\ncomplete-command grep file \n\nhook -group grep-highlight global WinSetOption filetype=grep %{\n    add-highlighter window/grep group\n    add-highlighter window/grep/ regex \"^([^:\\n]+):(\\d+):(\\d+)?\" 1:cyan 2:green 3:green\n    add-highlighter window/grep/ line %{%opt{jump_current_line}} default+b\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/grep }\n}\n\nhook global WinSetOption filetype=grep %{\n    hook buffer -group grep-hooks NormalKey <ret> jump\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks buffer grep-hooks }\n}\n\ndefine-command -hidden grep-jump %{\n    jump\n}\n\ndefine-command grep-next-match -docstring %{alias for \"jump-next *grep*\"} %{\n    jump-next -matching \\*grep(-.*)?\\*\n}\n\ndefine-command grep-previous-match -docstring %{alias for \"jump-previous *grep*\"} %{\n    jump-previous -matching \\*grep(-.*)?\\*\n}\n\n}\n\nhook -once global KakBegin .* %{ require-module grep }\n"
  },
  {
    "path": "rc/tools/jump.kak",
    "content": "declare-option -docstring \"name of the client in which all source code jumps will be executed\" \\\n    str jumpclient\ndeclare-option -docstring \"name of the client in which utilities display information\" \\\n    str toolsclient\n\nprovide-module jump %{\n\ndeclare-option -hidden int jump_current_line 0\n\ndefine-command -hidden jump %{\n    evaluate-commands -save-regs a %{ # use evaluate-commands to ensure jumps are collapsed\n        try %{\n            evaluate-commands -draft %{\n                execute-keys ',xs^([^:\\n]+):(\\d+):(\\d+)?<ret>'\n                set-register a %reg{1} %reg{2} %reg{3}\n            }\n            set-option buffer jump_current_line %val{cursor_line}\n            evaluate-commands -try-client %opt{jumpclient} -verbatim -- edit -existing -- %reg{a}\n            try %{ focus %opt{jumpclient} }\n        }\n    }\n}\n\ndefine-command jump-next -params 1.. -docstring %{\n    jump-next <bufname>: jump to next location listed in the given *grep*-like location list buffer.\n} %{\n    evaluate-commands -try-client %opt{jumpclient} -save-regs / %{\n        buffer %arg{@}\n        jump-select-next\n        jump\n    }\n    try %{\n        evaluate-commands -client %opt{toolsclient} %{\n            buffer %arg{@}\n            execute-keys gg %opt{jump_current_line}g\n        }\n    }\n}\ncomplete-command jump-next buffer\ndefine-command -hidden jump-select-next %{\n    # First jump to end of buffer so that if jump_current_line == 0\n    # 0g<a-l> will be a no-op and we'll jump to the first result.\n    # Yeah, thats ugly...\n    execute-keys ge %opt{jump_current_line}g<a-l> /^[^:\\n]+:\\d+:<ret>\n}\n\ndefine-command jump-previous -params 1.. -docstring %{\n    jump-previous <bufname>: jump to previous location listed in the given *grep*-like location list buffer.\n} %{\n    evaluate-commands -try-client %opt{jumpclient} -save-regs / %{\n        buffer %arg{@}\n        jump-select-previous\n        jump\n    }\n    try %{\n        evaluate-commands -client %opt{toolsclient} %{\n            buffer %arg{@}\n            execute-keys gg %opt{jump_current_line}g\n        }\n    }\n}\ncomplete-command jump-previous buffer\ndefine-command -hidden jump-select-previous %{\n    # See comment in jump-select-next\n    execute-keys ge %opt{jump_current_line}g<a-h> <a-/>^[^:\\n]+:\\d+:<ret>\n}\n\n}\n\nhook -once global KakBegin .* %{ require-module jump }\n"
  },
  {
    "path": "rc/tools/lint.asciidoc",
    "content": "= Integrate with tools that check files for problems.\n\nMany file-formats have \"lint\" tools that check for common problems and point out\nwhere they occur. Most of these tools produce output in the traditional message\nformat:\n\n----\n{filename}:{line}:{column}: {kind}: {message}\n----\n\nIf the 'kind' field contains 'error', the message is treated as an error,\notherwise it is assumed to be a warning.\n\nThe `:lint-buffer` and `:lint-selections` commands will run the shell command\nspecified in the `lintcmd` option, passing it the path to a temporary file\ncontaining the text to be linted. The results are collected in the\n`*lint-output*` buffer, and analyze it. If `toolsclient` is set, the\n`*lint-output*` buffer will be displayed in the named client.\n\nEach reported error or warning causes a marker to appear in the left-hand\nmargin of the buffer that was checked. When the main cursor moves onto that\nline, the associated messages are displayed. If they get distracting, you can\nturn off the markers and messages with the `:lint-hide-diagnostics` command.\n\nYou can also use `:lint-next-message` and `:lint-previous-message` to jump\nbetween the lines with messages.\n"
  },
  {
    "path": "rc/tools/lint.kak",
    "content": "# require-module jump\n\ndeclare-option \\\n    -docstring %{\n        The shell command used by lint-buffer and lint-selections.\n\n        See `:doc lint` for details.\n    } \\\n    str lintcmd\n\ndeclare-option -hidden line-specs lint_flags\ndeclare-option -hidden line-specs lint_messages\ndeclare-option -hidden int lint_error_count\ndeclare-option -hidden int lint_warning_count\n\ndefine-command -hidden -params 1 lint-open-output-buffer %{\n    evaluate-commands -try-client %opt{toolsclient} %{\n        edit! -fifo \"%arg{1}/fifo\" -debug *lint-output*\n        set-option buffer filetype make\n        set-option buffer jump_current_line 0\n    }\n}\n\ndefine-command \\\n    -hidden \\\n    -params 1 \\\n    -docstring %{\n        lint-cleaned-selections <linter>: Check each selection with <linter>.\n\n        Assumes selections all have anchor before cursor, and that\n        %val{selections} and %val{selections_desc} are in the same order.\n    } \\\n    lint-cleaned-selections \\\n%{\n    # Create a temporary directory to keep all our state.\n    evaluate-commands %sh{\n        # This is going to come in handy later.\n        kakquote() { printf \"%s\" \"$*\" | sed \"s/'/''/g; 1s/^/'/; \\$s/\\$/'/\"; }\n\n        # Before we clobber our arguments,\n        # let's record the lintcmd we were given.\n        lintcmd=\"$1\"\n\n        # Some linters care about the name or extension\n        # of the file being linted, so we'll store the text we want to lint\n        # in a file with the same name as the original buffer.\n        filename=\"${kak_buffile##*/}\"\n\n        # A directory to keep all our temporary data.\n        dir=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-lint.XXXXXXXX)\n\n        # Write all the selection descriptions to files.\n        eval set -- \"$kak_selections_desc\"\n        i=0\n        for desc; do\n            mkdir -p \"$dir\"/sel-\"$i\"\n            printf \"%s\" \"$desc\" > \"$dir\"/sel-$i/desc\n            i=$(( i + 1 ))\n        done\n\n        # Write all the selection contents to files.\n        eval set -- \"$kak_quoted_selections\"\n        i=0\n        for text; do\n            # The selection text needs to be stored in a subdirectory,\n            # so we can be sure the filename won't clash with one of ours.\n            mkdir -p \"$dir\"/sel-\"$i\"/text/\n            printf \"%s\" \"$text\" > \"$dir\"/sel-$i/text/\"$filename\"\n            i=$(( i + 1 ))\n        done\n\n        # We do redirection trickiness to record stderr from\n        # this background task and route it back to Kakoune,\n        # but shellcheck isn't a fan.\n        # shellcheck disable=SC2094\n        ({ # do the parsing in the background and when ready send to the session\n        trap - INT QUIT\n\n        for selpath in \"$dir\"/sel-*; do\n            # Read in the line and column offset of this selection.\n            IFS=\".,\" read -r start_line start_byte _ < \"$selpath\"/desc\n\n            # Run the linter, and record the exit-code.\n            eval \"$lintcmd '$selpath/text/$filename'\" |\n                    sort -t: -k2,2 -n |\n                    awk \\\n                        -v line_offset=$(( start_line - 1 )) \\\n                        -v first_line_byte_offset=$(( start_byte - 1 )) \\\n                    '\n                        BEGIN { OFS=\":\"; FS=\":\" }\n\n                        /:[1-9][0-9]*:[1-9][0-9]*:/ {\n                            $1 = ENVIRON[\"kak_bufname\"]\n                            if ( $2 == 1 ) {\n                                $3 += first_line_byte_offset\n                            }\n                            $2 += line_offset\n                            print $0\n                        }\n                    ' >>\"$dir\"/result\n        done\n\n        # Load all the linter messages into Kakoune options.\n        # Inside this block, shellcheck warns us that the shell doesn't\n        # need backslash-continuation chars in a single-quoted string,\n        # but awk still needs them.\n        # shellcheck disable=SC1004\n        awk -v file=\"$kak_buffile\" -v stamp=\"$kak_timestamp\" -v client=\"$kak_client\" '\n            function kakquote(text) {\n                # \\x27 is apostrophe, escaped for shell-quoting reasons.\n                gsub(/\\x27/, \"\\x27\\x27\", text)\n                return \"\\x27\" text \"\\x27\"\n            }\n\n            BEGIN {\n                OFS=\":\"\n                FS=\":\"\n                error_count = 0\n                warning_count = 0\n            }\n\n            /:[1-9][0-9]*:[1-9][0-9]*:/ {\n                # Remember that an error or a warning occurs on this line..\n                if ($4 ~ /[Ee]rror/) {\n                    # We definitely have an error on this line.\n                    flags_by_line[$2] = \"{Error}x\"\n                    error_count++\n                } else if (flags_by_line[$2] ~ /Error/) {\n                    # We have a warning on this line,\n                    # but we already have an error, so do nothing.\n                    warning_count++\n                } else {\n                    # We have a warning on this line,\n                    # and no previous error.\n                    flags_by_line[$2] = \"{Information}!\"\n                    warning_count++\n                }\n\n                # The message starts with the severity indicator.\n                msg = substr($4, 2)\n\n                # fix case where $5 is not the last field\n                # because of extra colons in the message\n                for (i=5; i<=NF; i++) msg = msg \":\" $i\n\n                # Mention the column where this problem occurs,\n                # so that information is not lost.\n                msg = msg \"(col \" $3 \")\"\n\n                # Messages will be stored in a line-specs option,\n                # and each record in the option uses \"|\"\n                # as a field delimiter, so we need to escape them.\n                gsub(/\\|/, \"\\\\|\", msg)\n\n                if ($2 in messages_by_line) {\n                    # We already have a message on this line,\n                    # so append our new message.\n                    messages_by_line[$2] = messages_by_line[$2] \"\\n\" msg\n                } else {\n                    # A brand-new message on this line.\n                    messages_by_line[$2] = msg\n                }\n            }\n\n            END {\n                printf(\"set-option %s lint_flags %s\", kakquote(\"buffer=\" file), stamp);\n                for (line in flags_by_line) {\n                    flag = flags_by_line[line]\n                    printf(\" %s\", kakquote(line \"|\" flag));\n                }\n                printf(\"\\n\");\n\n                printf(\"set-option %s lint_messages %s\", kakquote(\"buffer=\" file), stamp);\n                for (line in messages_by_line) {\n                    msg = messages_by_line[line]\n                    printf(\" %s\", kakquote(line \"|\" msg));\n                }\n                printf(\"\\n\");\n\n                print \"set-option \" \\\n                    kakquote(\"buffer=\" file) \" \" \\\n                    \"lint_error_count \" \\\n                    error_count\n                print \"set-option \" \\\n                    kakquote(\"buffer=\" file) \" \" \\\n                    \"lint_warning_count \" \\\n                    warning_count\n            }\n        ' \"$dir\"/result | kak -p \"$kak_session\"\n\n        # Send any linting errors to the debug buffer,\n        # for visibility.\n        if [ -s \"$dir\"/stderr ]; then\n            # Errors were detected!\"\n            printf \"echo -debug Linter errors: <<<\\n\"\n            while read -r LINE; do\n                printf \"echo -debug %s\\n\" \"$(kakquote \"  $LINE\")\"\n            done < \"$dir\"/stderr\n            printf \"echo -debug >>>\\n\"\n            # FIXME: When #3254 is fixed, this can become a \"fail\"\n            printf \"eval -client %s echo -markup {Error}%s\\n\" \\\n                \"$kak_client\" \\\n                \"lint failed, see *debug* for details\"\n        else\n            # No errors detected, show the results.\n            printf \"eval -client %s 'lint-show-diagnostics; lint-show-counters'\" \\\n                \"$kak_client\"\n        fi | kak -p \"$kak_session\"\n\n        # A fifo to send the results back to a Kakoune buffer.\n        mkfifo \"$dir\"/fifo\n        # Send the results to kakoune if the session is still valid.\n        if printf 'lint-open-output-buffer %s' \"$(kakquote \"$dir\")\" | kak -p \"$kak_session\"; then\n            cat \"$dir\"/result > \"$dir\"/fifo\n        fi\n        # Clean up.\n        rm -rf \"$dir\"\n\n        } & ) >\"$dir\"/stderr 2>&1 </dev/null\n    }\n}\n\ndefine-command \\\n    -params 0..2 \\\n    -docstring %{\n        lint-selections [<switches>]: Check each selection with a linter.\n\n        Switches:\n            -command <cmd>      Use the given linter.\n                                If not given, the lintcmd option is used.\n\n        See `:doc lint` for details.\n    } \\\n    lint-selections \\\n%{\n    evaluate-commands -draft %{\n        # Make sure all the selections are \"forward\" (anchor before cursor)\n        execute-keys <a-:>\n\n        # Make sure the selections are in document order.\n        evaluate-commands %sh{\n            printf \"select \"\n            printf \"%s\\n\" \"$kak_selections_desc\" |\n                tr ' ' '\\n' |\n                sort -n -t. |\n                tr '\\n' ' '\n        }\n\n        evaluate-commands %sh{\n            # This is going to come in handy later.\n            kakquote() { printf \"%s\" \"$*\" | sed \"s/'/''/g; 1s/^/'/; \\$s/\\$/'/\"; }\n\n            if [ \"$1\" = \"-command\" ]; then\n                if [ -z \"$2\" ]; then\n                    echo 'fail -- -command option requires a value'\n                    exit 1\n                fi\n                lintcmd=\"$2\"\n            elif [ -n \"$1\" ]; then\n                echo \"fail -- Unrecognised parameter $(kakquote \"$1\")\"\n                exit 1\n            elif [ -z \"${kak_opt_lintcmd}\" ]; then\n                echo 'fail The lintcmd option is not set'\n                exit 1\n            else\n                lintcmd=\"$kak_opt_lintcmd\"\n            fi\n\n            printf 'lint-cleaned-selections %s\\n' \"$(kakquote \"$lintcmd\")\"\n        }\n    }\n}\n\ndefine-command \\\n    -docstring %{\n        lint-buffer: Check the current buffer with a linter.\n\n        See `:doc lint` for details.\n    } \\\n    lint-buffer \\\n%{\n    evaluate-commands %sh{\n        if [ -z \"${kak_opt_lintcmd}\" ]; then\n            echo 'fail The lintcmd option is not set'\n            exit 1\n        fi\n    }\n    evaluate-commands -draft %{\n        execute-keys '%'\n        lint-cleaned-selections %opt{lintcmd}\n    }\n}\n\nalias global lint lint-buffer\n\ndefine-command -hidden lint-show-current-line %{\n    update-option buffer lint_messages\n    evaluate-commands %sh{\n        # This is going to come in handy later.\n        kakquote() { printf \"%s\" \"$*\" | sed \"s/'/''/g; 1s/^/'/; \\$s/\\$/'/\"; }\n\n        eval set -- \"${kak_quoted_opt_lint_messages}\"\n        shift # skip the timestamp\n\n        while [ $# -gt 0 ]; do\n            lineno=${1%%|*}\n            msg=${1#*|}\n\n            if [ \"$lineno\" -eq \"$kak_cursor_line\" ]; then\n                printf \"info -anchor %d.%d %s\\n\" \\\n                    \"$kak_cursor_line\" \\\n                    \"$kak_cursor_column\" \\\n                    \"$(kakquote \"$msg\")\"\n                break\n            fi\n            shift\n        done\n    }\n}\n\ndefine-command -hidden lint-show-counters %{\n    echo -markup \"linting results: {Error} %opt{lint_error_count} error(s) {Information} %opt{lint_warning_count} warning(s) \"\n}\n\ndefine-command -hidden lint-show-diagnostics %{\n    try %{\n        # Assume that if the highlighter is set, then hooks also are\n        add-highlighter window/lint flag-lines default lint_flags\n        hook window -group lint-diagnostics NormalIdle .* %{ lint-show-current-line }\n        hook window -group lint-diagnostics WinSetOption lint_flags=.* %{ info; lint-show-current-line }\n    }\n}\n\ndefine-command lint-hide-diagnostics -docstring \"Hide line markers and disable automatic diagnostic displaying\" %{\n    remove-highlighter window/lint\n    remove-hooks window lint-diagnostics\n}\n\n# FIXME: Is there some way we can re-use make-next-error\n# instead of re-implementing it?\ndefine-command \\\n    -docstring \"Jump to the next line that contains a lint message\" \\\n    lint-next-message \\\n%{\n    update-option buffer lint_messages\n\n    evaluate-commands %sh{\n        # This is going to come in handy later.\n        kakquote() { printf \"%s\" \"$*\" | sed \"s/'/''/g; 1s/^/'/; \\$s/\\$/'/\"; }\n\n        eval \"set -- ${kak_quoted_opt_lint_messages}\"\n        shift\n\n        if [ \"$#\" -eq 0 ]; then\n            printf 'fail no lint messages'\n            exit\n        fi\n\n        first_lineno=\"\"\n        first_msg=\"\"\n\n        for lint_message; do\n            lineno=\"${lint_message%%|*}\"\n            msg=\"${lint_message#*|}\"\n\n            if [ -z \"$first_lineno\" ]; then\n                first_lineno=$lineno\n                first_msg=$msg\n            fi\n\n            if [ \"$lineno\" -gt \"$kak_cursor_line\" ]; then\n                printf \"execute-keys %dg\\n\" \"$lineno\"\n                printf \"info -anchor %d.%d %s\\n\" \\\n                    \"$lineno\" \"1\" \"$(kakquote \"$msg\")\"\n                exit\n            fi\n        done\n\n        # We didn't find any messages after the current line,\n        # let's wrap around to the beginning.\n        printf \"execute-keys %dg\\n\" \"$first_lineno\"\n        printf \"info -anchor %d.%d %s\\n\" \\\n            \"$first_lineno\" \"1\" \"$(kakquote \"$first_msg\")\"\n        printf \"echo -markup \\\n            {Information}lint message search wrapped around buffer\\n\"\n\n    }\n}\n\n# FIXME: Is there some way we can re-use make-previous-error\n# instead of re-implementing it?\ndefine-command \\\n    -docstring \"Jump to the previous line that contains a lint message\" \\\n    lint-previous-message \\\n%{\n    update-option buffer lint_messages\n\n    evaluate-commands %sh{\n        # This is going to come in handy later.\n        kakquote() { printf \"%s\" \"$*\" | sed \"s/'/''/g; 1s/^/'/; \\$s/\\$/'/\"; }\n\n        eval \"set -- ${kak_quoted_opt_lint_messages}\"\n        shift\n\n        if [ \"$#\" -eq 0 ]; then\n            printf 'fail no lint messages'\n            exit\n        fi\n\n        prev_lineno=\"\"\n        prev_msg=\"\"\n\n        for lint_message; do\n            lineno=\"${lint_message%%|*}\"\n            msg=\"${lint_message#*|}\"\n\n            # If this message comes on or after the cursor position...\n            if [ \"$lineno\" -ge \"${kak_cursor_line}\" ]; then\n                # ...and we had a previous message...\n                if [ -n \"$prev_lineno\" ]; then\n                    # ...then go to the previous message and display it.\n                    printf \"execute-keys %dg\\n\" \"$prev_lineno\"\n                    printf \"info -anchor %d.%d %s\\n\" \\\n                        \"$lineno\" \"1\" \"$(kakquote \"$prev_msg\")\"\n                    exit\n\n                # We are after the cursor position, but there has been\n                # no previous message; we'll need to do something else.\n                else\n                    break\n                fi\n            fi\n\n            # We have not yet reached the cursor position, stash this message\n            # and try the next.\n            prev_lineno=\"$lineno\"\n            prev_msg=\"$msg\"\n        done\n\n        # There is no message before the cursor position,\n        # let's wrap around to the end.\n        shift $(( $# - 1 ))\n        last_lineno=\"${1%%|*}\"\n        last_msg=\"${1#*|}\"\n\n        printf \"execute-keys %dg\\n\" \"$last_lineno\"\n        printf \"info -anchor %d.%d %s\\n\" \\\n            \"$last_lineno\" \"1\" \"$(kakquote \"$last_msg\")\"\n        printf \"echo -markup \\\n            {Information}lint message search wrapped around buffer\\n\"\n    }\n}\n"
  },
  {
    "path": "rc/tools/make.kak",
    "content": "declare-option -docstring \"shell command run to build the project\" \\\n    str makecmd make\ndeclare-option -docstring \"pattern that describes lines containing information about errors in the output of the `makecmd` command. Capture groups must be: 1: filename 2: line number 3: optional column 4: optional error description\" \\\n    regex make_error_pattern \"^([^:\\n]+):(\\d+):(?:(\\d+):)? (?:fatal )?error:([^\\n]+)?\"\n\nprovide-module make %{\n\nrequire-module fifo\nrequire-module jump\n\ndefine-command -params .. -docstring %{\n    make [<arguments>]: make utility wrapper\n    All the optional arguments are forwarded to the make utility\n} make %{\n    evaluate-commands -save-regs m %{\n        set-register m %opt{makecmd}\n        evaluate-commands -try-client %opt{toolsclient} %{\n            fifo -scroll -name *make* -script %{\n                trap - INT QUIT\n                eval \"$kak_reg_m \\\"\\$@\\\"\"\n            } -- %arg{@}\n            set-option buffer filetype make\n            set-option buffer jump_current_line 0\n        }\n    }\n}\n\nadd-highlighter shared/make group\nadd-highlighter shared/make/ regex \"^([^:\\n]+):(\\d+):(?:(\\d+):)?\\h+(?:((?:fatal )?error)|(warning)|(note)|(required from(?: here)?))?.*?$\" 1:cyan 2:green 3:green 4:red 5:yellow 6:blue 7:yellow\nadd-highlighter shared/make/ regex \"^\\h*(~*(?:(\\^)~*)?)$\" 1:green 2:cyan+b\nadd-highlighter shared/make/ line '%opt{jump_current_line}' default+b\n\nhook -group make-highlight global WinSetOption filetype=make %{\n    add-highlighter window/make ref make\n    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/make }\n}\n\nhook global WinSetOption filetype=make %{\n    alias buffer jump make-jump\n    alias buffer jump-select-next make-select-next\n    alias buffer jump-select-previous make-select-previous\n    hook buffer -group make-hooks NormalKey <ret> make-jump\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks buffer make-hooks }\n}\n\ndefine-command -hidden make-open-error -params 4 %{\n    evaluate-commands -try-client %opt{jumpclient} %{\n        edit -existing \"%arg{1}\" %arg{2} %arg{3}\n        echo -markup \"{Information}{\\}%arg{4}\"\n        try %{ focus }\n    }\n}\n\ndefine-command -hidden make-jump %{\n    evaluate-commands -save-regs a/ %{\n        evaluate-commands -draft %{\n            execute-keys ,\n            try %{\n                execute-keys gl<a-?> \"Entering directory\" <ret><a-:>\n                # Try to parse the error into capture groups, failing on absolute paths\n                execute-keys s \"Entering directory [`']([^']+)'.*\\n([^:\\n/][^:\\n]*):(\\d+):(?:(\\d+):)?([^\\n]+)\\n?\\z\" <ret>l\n                set-option buffer jump_current_line %val{cursor_line}\n                set-register a \"%reg{1}/%reg{2}\" \"%reg{3}\" \"%reg{4}\" \"%reg{5}\"\n            } catch %{\n                # check if error pattern matches exactly at the start of the current line, possibly spanning more lines\n                execute-keys ghGe \n                try %{\n                    set-register / \"\\A%opt{make_error_pattern}\"\n                    execute-keys s<ret>l\n                } catch %{ # fallback on common error pattern so that explicit jumps on warning/note lines still work\n                    set-register / \"\\A^([^:\\n]+):(\\d+):(?:(\\d+):)? ([^\\n]+)?\"\n                    execute-keys s<ret>l\n                }\n                set-option buffer jump_current_line %val{cursor_line}\n                set-register a \"%reg{1}\" \"%reg{2}\" \"%reg{3}\" \"%reg{4}\"\n            }\n        }\n        make-open-error %reg{a}\n    }\n}\ndefine-command -hidden make-select-next %{\n        set-register / %opt{make_error_pattern}\n        # go to the current line end, search and go to the start of selection\n        execute-keys \"%opt{jump_current_line}ggl\" \"/<ret><a-;>;\"\n}\ndefine-command -hidden make-select-previous %{\n        set-register / %opt{make_error_pattern}\n        execute-keys \"%opt{jump_current_line}g\" \"<a-/><ret><a-;>;\"\n}\n\ndefine-command make-next-error -docstring %{alias for \"jump-next *make*\"} %{\n    jump-next *make*\n}\n\ndefine-command make-previous-error -docstring %{alias for \"jump-previous *make*\"} %{\n    jump-previous *make*\n}\n\n}\n\nhook -once global KakBegin .* %{ require-module make }\n"
  },
  {
    "path": "rc/tools/man.kak",
    "content": "declare-option -docstring \"name of the client in which documentation is to be displayed\" \\\n    str docsclient\n\ndeclare-option -hidden str-list manpage\n\nhook -group man-highlight global WinSetOption filetype=man %{\n    add-highlighter window/man-highlight group\n    # Sections\n    add-highlighter window/man-highlight/ regex ^\\S.*?$ 0:title\n    # Subsections\n    add-highlighter window/man-highlight/ regex '^ {3}\\S.*?$' 0:default+b\n    # Command line options\n    add-highlighter window/man-highlight/ regex '^ {7}-[^\\s,]+(,\\s+-[^\\s,]+)*' 0:list\n    # References to other manpages\n    add-highlighter window/man-highlight/ regex [-a-zA-Z0-9_.]+\\([a-z0-9]+\\) 0:header\n\n    map window normal <ret> :man-jump<ret>\n\n    hook -once -always window WinSetOption filetype=.* %{\n      remove-highlighter window/man-highlight\n      unmap window normal <ret>\n    }\n}\n\nhook global WinSetOption filetype=man %{\n    hook -group man-hooks window WinResize .* %{ man-impl %opt{manpage} }\n    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window man-hooks }\n}\n\ndefine-command -hidden -params ..3 man-impl %{ evaluate-commands %sh{\n    buffer_name=\"$1\"\n    if [ -z \"${buffer_name}\" ]; then\n        exit\n    fi\n    shift\n    manout=$(mktemp \"${TMPDIR:-/tmp}\"/kak-man.XXXXXX)\n    manerr=$(mktemp \"${TMPDIR:-/tmp}\"/kak-man.XXXXXX)\n    colout=$(mktemp \"${TMPDIR:-/tmp}\"/kak-man.XXXXXX)\n    env MANWIDTH=${kak_window_range##* } man \"$@\" > \"$manout\" 2> \"$manerr\"\n    retval=$?\n    if command -v col >/dev/null; then\n        col -b -x > ${colout} < ${manout}\n    else\n        sed 's/.\b//g' > ${colout} < ${manout}\n    fi\n    rm ${manout}\n\n    if [ \"${retval}\" -eq 0 ]; then\n        printf %s\\\\n \"\n                edit -scratch %{*$buffer_name ${*}*}\n                execute-keys '%|cat<space>${colout}<ret>gk'\n                nop %sh{ rm ${colout}; rm ${manerr} }\n                set-option buffer filetype man\n                set-option window manpage $buffer_name $*\n        \"\n    else\n        printf '\n            fail %%{%s}\n            nop %%sh{ rm \"%s\"; rm \"%s\" }\n        ' \"$(cat \"$manerr\")\" \"${colout}\" \"${manerr}\"\n    fi\n} }\n\ndefine-command -params ..1 \\\n    -shell-script-candidates %{\n        find /usr/share/man/ $(printf %s \"${MANPATH}\" |\n            sed 's/:/ /') -name '*.[1-8]*' |\n            sed 's,^.*/\\(.*\\)\\.\\([1-8][a-zA-Z]*\\).*$,\\1(\\2),'\n    } \\\n    -docstring %{\n        man [<page>]: manpage viewer wrapper\n        If no argument is passed to the command, the selection will be used as page\n        The page can be a word, or a word directly followed by a section number between parenthesis, e.g. kak(1)\n    } man %{ evaluate-commands %sh{\n    subject=${1-$kak_selection}\n\n    ## The completion suggestions display the page number, strip them if present\n    case \"${subject}\" in\n        *\\([1-8]*\\))\n            pagenum=\"${subject##*\\(}\"\n            pagenum=\"${pagenum%\\)}\"\n            subject=\"${subject%%\\(*}\"\n            ;;\n        *)\n            pagenum=\"\"\n            ;;\n    esac\n\n    printf %s\\\\n \"evaluate-commands -try-client %opt{docsclient} man-impl man $pagenum $subject\"\n} }\n\n\n\n# The following section of code enables a user\n# to go to next or previous man page links and to follow man page links,\n# for example, apropos(1), that would normally appear in SEE ALSO sections.\n# The user would position the cursor on any character of the link\n# and then press <ret> to change to a buffer showing the man page.\n\n# Regex pattern defining a man page link.\n# Used for determining if a selection, which may just be a link, is a link.\ndeclare-option -hidden regex man_link1 \\\n  [\\w_.:-]+\\(\\d[a-z]*\\)\n\n# Same as above but with lookbehind and lookahead patterns.\n# Used for searching for a man page link.\ndeclare-option -hidden regex man_link2 \\\n  \"(?:^|(?<=\\W))%opt{man_link1}(?=\\W)\"\n\n# Define a useful command sequence for searching a given regex\n# and a given sequence of search keys.\ndefine-command -hidden man-search -params 2 %{\n    set-register / %arg[1]\n    try %{\n        execute-keys %arg[2]\n    } catch %{\n        fail \"Could not find man page link\"\n    }\n}\n\ndefine-command -docstring 'Go to next man page link' \\\nman-link-next %{ man-search %opt[man_link2] n }\n\ndefine-command -docstring 'Go to previous man page link' \\\nman-link-prev %{ man-search %opt[man_link2] <a-n> }\n\ndefine-command -docstring 'Try to jump to a man page' \\\nman-jump %{\n  try %{ execute-keys <a-a><a-w> s %opt[man_link1] <ret> } catch %{ fail 'Not a valid man page link' }\n  try %{ man } catch %{ fail 'No man page link to follow' }\n}\n\n# Suggested keymaps for a user mode\ndeclare-user-mode man\n\nmap global man 'g' -docstring 'Jump to a man page using selected man page link' :man-jump<ret>\nmap global man 'j' -docstring 'Go to next man page link'                        :man-link-next<ret>\nmap global man 'k' -docstring 'Go to previous man page link'                    :man-link-prev<ret>\nmap global man 'm' -docstring 'Look up a man page'                              :man<space>\n"
  },
  {
    "path": "rc/tools/menu.kak",
    "content": "provide-module menu %§§\n\ndefine-command menu -params 1.. -docstring %{\n    menu [<switches>] <name1> <commands1> <name2> <commands2>...: display a \n    menu and execute commands for the selected item\n\n    -auto-single instantly validate if only one item is available\n    -select-cmds each item specify an additional command to run when selected\n} %{\n    evaluate-commands %sh{\n        auto_single=false\n        select_cmds=false\n        stride=2\n        on_abort=\n        while true\n        do\n            case \"$1\" in\n                (-auto-single) auto_single=true ;;\n                (-select-cmds) select_cmds=true; stride=3 ;;\n                (-on-abort) on_abort=\"$2\"; shift ;;\n                (-markup) ;; # no longer supported\n                (*) break ;;\n            esac\n            shift\n        done\n        if [ $(( $# % $stride )) -ne 0 ]; then\n            echo fail \"wrong argument count\"\n            exit\n        fi\n        if $auto_single && [ $# -eq $stride ]; then\n            printf %s \"$2\"\n            exit\n        fi\n        shellquote() {\n            printf \"'%s'\" \"$(printf %s \"$1\" | sed \"s/'/'\\\\\\\\''/g; s/§/§§/g; $2\")\"\n        }\n        cases=\n        select_cases=\n        completion=\n        nl=$(printf '\\n.'); nl=${nl%.}\n        while [ $# -gt 0 ]; do\n            title=$1\n            command=$2\n            completion=\"${completion}${title}${nl}\"\n            cases=\"${cases}\n                ($(shellquote \"$title\" s/¶/¶¶/g))\n                    printf '%s\\\\n' $(shellquote \"$command\" s/¶/¶¶/g)\n                    ;;\"\n            if $select_cmds; then\n                select_command=$3\n                select_cases=\"${select_cases}\n                    ($(shellquote \"$title\" s/¶/¶¶/g))\n                        printf '%s\\\\n' $(shellquote \"$select_command\" s/¶/¶¶/g)\n                        ;;\"\n            fi\n            shift $stride\n        done\n        printf \"\\\n            prompt '' %%§\n                    evaluate-commands %%sh¶\n                        case \\\"\\$kak_text\\\" in \\\n                        %s\n                        (*) echo fail -- no such item: \\\"'\\$(printf %%s \\\"\\$kak_text\\\" | sed \\\"s/'/''/g\\\")'\\\" ;;\n                        esac\n                    ¶\n                §\" \"$cases\"\n        if $select_cmds; then\n            printf \" \\\n                    -on-change %%§\n                        evaluate-commands %%sh¶\n                            case \\\"\\$kak_text\\\" in \\\n                            %s\n                            (*) : ;;\n                            esac\n                        ¶\n                    §\" \"$select_cases\"\n        fi\n        if [ -n \"$on_abort\" ]; then\n            printf \" -on-abort '%s'\" \"$(printf %s \"$on_abort\" | sed \"s/'/''/g\")\"\n        fi\n        printf ' -menu -shell-script-candidates %%§\n                    printf %%s %s\n                §\\n' \"$(shellquote \"$completion\")\"\n    }\n}\n"
  },
  {
    "path": "rc/tools/patch-range.pl",
    "content": "#!/usr/bin/env perl\n\nuse strict;\nuse warnings;\n\nmy $print_remaining = 0;\nif ($ARGV[0] eq \"-print-remaining-diff\") {\n    $print_remaining = 1;\n    shift @ARGV;\n}\n\nmy $line_number_kind = \"diff\";\nif ($ARGV[0] eq \"-line-numbers-from-new-file\") {\n    $line_number_kind = \"new-file\";\n    shift @ARGV;\n}\n\nmy $min_line = $ARGV[0];\nshift @ARGV;\nmy $max_line = $ARGV[0];\nshift @ARGV;\n\nmy $patch_cmd;\nif (defined $ARGV[0] and $ARGV[0] =~ m{^[^-]}) {\n    $patch_cmd = \"@ARGV\";\n} else {\n    $patch_cmd = \"patch @ARGV\";\n}\nmy $reverse = grep /^(--reverse|-R)$/, @ARGV;\n\nmy $lineno = $line_number_kind eq \"diff\" ? 0 : undef;\nmy $original = \"\";\nmy $diff_header = \"\";\nmy $wheat = \"\";\nmy $chaff = \"\" if $print_remaining;\nmy $state = undef;\nmy $hunk_wheat = undef;\nmy $hunk_chaff = undef if $print_remaining;\nmy $hunk_header = undef;\nmy $hunk_remaining_lines = undef;\nmy $signature = \"\" if $print_remaining;\n\nsub compute_hunk_header {\n    my $original_header = shift;\n    my $hunk = shift;\n    my $old_lines = 0;\n    my $new_lines = 0;\n    for (split /\\n/, $hunk) {\n        $old_lines++ if m{^[ -]};\n        $new_lines++ if m{^[ +]};\n    }\n    my $updated_header = $original_header =~ s/^@@ -(\\d+),\\d+\\s+\\+(\\d+),\\d+ @@(.*)/@@ -$1,$old_lines +$2,$new_lines @\\@$3/mr;\n    return $updated_header;\n}\n\nsub finish_hunk {\n    return unless defined $hunk_header;\n    if ($hunk_wheat =~ m{^[-+]}m) {\n        if ($diff_header) {\n            $wheat .= $diff_header;\n            $diff_header = \"\";\n        }\n        $wheat .= (compute_hunk_header $hunk_header, $hunk_wheat). $hunk_wheat;\n    }\n    if ($print_remaining) {\n        $chaff .= (compute_hunk_header $hunk_header, $hunk_chaff) . $hunk_chaff . $signature;\n    }\n    $hunk_header = undef;\n}\n\nwhile (<STDIN>) {\n    ++$lineno if $line_number_kind eq \"diff\";\n    $original .= $_;\n    if (m{^diff} || (not defined $state and m{^---})) {\n        finish_hunk();\n        $state = \"diff header\";\n        $diff_header = \"\";\n    }\n    if ($state eq \"signature\") {\n        $signature .= $_ if $print_remaining;\n        next;\n    }\n    if (m{^@@ -\\d+(?:,(\\d)+)? \\+(\\d+)(?:,\\d+)? @@}) {\n        $lineno = $2 - 1 if $line_number_kind eq \"new-file\";\n        $hunk_remaining_lines = $1 or 1;\n        finish_hunk();\n        $state = \"diff hunk\";\n        $hunk_header = $_;\n        $hunk_wheat = \"\";\n        if ($print_remaining) {\n            $hunk_chaff = \"\";\n            $signature = \"\";\n        }\n        next;\n    }\n    if ($state eq \"diff header\") {\n        $diff_header .= $_;\n        $chaff .= $_ if $print_remaining;\n        next;\n    }\n    if ($hunk_remaining_lines == 0 and m{^-- $}) {\n        $state = \"signature\";\n        $signature .= $_ if $print_remaining;\n        next;\n    }\n    ++$lineno if $line_number_kind eq \"new-file\" && m{^[ +]};\n    --$hunk_remaining_lines if m{^[ -]};\n    my $include = m{^ } ||\n        ($lineno >= $min_line && $lineno <= $max_line) ||\n        ($line_number_kind eq \"new-file\" && m{^-} && $lineno == $min_line - 1);\n    if ($include) {\n        $hunk_wheat .= $_;\n        if ($print_remaining) {\n            $hunk_chaff .= $_ if m{^ };\n            if ($reverse ? m{^[-]} : m{^\\+}) {\n                $hunk_chaff .= \" \" . substr $_, 1;\n            }\n        }\n    } else {\n        if ($reverse ? m{^\\+} : m{^-}) {\n            $hunk_wheat .= \" \" . substr $_, 1;\n        }\n        $hunk_chaff .= $_ if $print_remaining;\n    }\n}\nfinish_hunk();\n\nopen PATCH_COMMAND, \"|-\", \"$patch_cmd\" or die \"patch-range.pl: error running '$patch_cmd': $!\";\nprint PATCH_COMMAND $wheat;\nif (not close PATCH_COMMAND) {\n    print $original;\n    print STDERR \"patch-range.pl: error running:\\n\" . \"\\$ $patch_cmd << EOF\\n$wheat\" . \"EOF\\n\";\n    exit 1;\n}\nprint $chaff if $print_remaining;\n"
  },
  {
    "path": "rc/tools/patch.kak",
    "content": "define-command patch -params .. -docstring %{\n    patch [<arguments>]: apply selections in diff to a file\n\n    Given some selections within a unified diff, apply the changed lines in\n    each selection by piping them to \"patch <arguments> 1>&2\"\n    (or \"<arguments> 1>&2\" if <arguments> starts with a non-option argument).\n    If successful, the in-buffer diff will be updated to reflect the applied\n    changes.\n    For selections that contain no newline, the entire enclosing diff hunk\n    is applied (unless the cursor is inside a diff header, in which case\n    the entire diff is applied).\n    To revert changes, <arguments> must contain \"--reverse\" or \"-R\".\n} %{\n    evaluate-commands -draft -itersel -save-regs aes|^ %{\n        try %{\n            execute-keys <a-k>\\n<ret>\n        } catch %{\n            # The selection contains no newline.\n            execute-keys -save-regs '' Z\n            execute-keys <a-l><semicolon><a-?>^diff<ret>\n            try %{\n                execute-keys <a-k>^@@<ret>\n                # If the cursor is in a diff hunk, stage the entire hunk.\n                execute-keys z\n                execute-keys /.*?(?:(?=\\n@@)|(?=\\ndiff)|(?=\\n\\n)|\\z)<ret>x<semicolon><a-?>^@@<ret>\n            } catch %{\n                # If the cursor is in a diff header, stage the entire diff.\n                execute-keys <a-semicolon>?.*?(?:(?=\\ndiff)|(?=\\n\\n)|\\z)<ret>\n            }\n        }\n        # We want to apply only the selected lines. Remember them.\n        execute-keys <a-:>\n        set-register s %val{selection_desc}\n        # Select forward until the end of the last hunk.\n        execute-keys H?.*?(?:(?=\\n@@)|(?=\\ndiff)|(?=\\n\\n)|\\z)<ret>x\n        # Select backward to the beginning of the first hunk's diff header.\n        execute-keys <a-semicolon><a-L><a-?>^diff<ret>\n        # Move cursor to the beginning so we know the diff's offset within the buffer.\n        execute-keys <a-:><a-semicolon>\n        set-register a %arg{@}\n        set-register e nop\n        set-register | %{\n            # The selected range to apply.\n            IFS=' .,' read min_line _ max_line _ <<-EOF\n            $kak_reg_s\n\tEOF\n            min_line=$((min_line - kak_cursor_line + 1))\n            max_line=$((max_line - kak_cursor_line + 1))\n\n            # Since registers are never empty, we get an empty arg even if\n            # there were no args. This does no harm because we pass it to\n            # a shell where it expands to nothing.\n            eval set -- \"$kak_quoted_reg_a\"\n\n            perl \"${kak_runtime}\"/rc/tools/patch-range.pl -print-remaining-diff $min_line $max_line \"$@\" \">&2\" ||\n                echo >$kak_command_fifo \"set-register e fail 'patch: failed to apply selections, see *debug* buffer'\"\n        }\n        execute-keys |<ret>\n        %reg{e}\n    }\n}\n\nprovide-module patch %§§\n"
  },
  {
    "path": "rc/tools/python/jedi.kak",
    "content": "hook -once global BufSetOption filetype=python %{\n    require-module jedi\n}\n\nprovide-module jedi %{\n\ndeclare-option -hidden str jedi_tmp_dir\ndeclare-option -hidden completions jedi_completions\ndeclare-option -docstring \"colon separated list of path added to `python`'s $PYTHONPATH environment variable\" \\\n    str jedi_python_path\n\ndefine-command jedi-complete -docstring \"Complete the current selection\" %{\n    evaluate-commands %sh{\n        dir=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-jedi.XXXXXXXX)\n        mkfifo ${dir}/fifo\n        printf %s\\\\n \"set-option buffer jedi_tmp_dir ${dir}\"\n        printf %s\\\\n \"evaluate-commands -no-hooks write -sync ${dir}/buf\"\n    }\n    evaluate-commands %sh{\n        dir=${kak_opt_jedi_tmp_dir}\n        printf %s\\\\n \"evaluate-commands -draft %{ edit! -fifo ${dir}/fifo *jedi-output* }\"\n        ((\n            trap - INT QUIT\n            cd $(dirname ${kak_buffile})\n\n            export PYTHONPATH=\"$kak_opt_jedi_python_path:$PYTHONPATH\"\n            python 2> \"${dir}/fifo\" -c 'if 1:\n                import os\n                dir = os.environ[\"kak_opt_jedi_tmp_dir\"]\n                buffile = os.environ[\"kak_buffile\"]\n                line = int(os.environ[\"kak_cursor_line\"])\n                column = int(os.environ[\"kak_cursor_column\"])\n                timestamp = os.environ[\"kak_timestamp\"]\n                client = os.environ[\"kak_client\"]\n                pipe_escape = lambda s: s.replace(\"|\", \"\\\\|\")\n                def quote(s):\n                    c = chr(39) # single quote\n                    return c + s.replace(c, c+c) + c\n                import jedi\n                script = jedi.Script(code=open(dir + \"/buf\", \"r\").read(), path=buffile)\n                completions = (\n                    quote(\n                        pipe_escape(str(c.name)) + \"|\" +\n                        pipe_escape(\"info -style menu -- \" + quote(c.docstring())) + \"|\" +\n                        pipe_escape(str(c.name))\n                    )\n                    for c in script.complete(line=line, column=column-1)\n                )\n                header = str(line) + \".\" + str(column) + \"@\" + timestamp\n                cmds = [\n                    \"echo completed\",\n                    \" \".join((\"set-option\", quote(\"buffer=\" + buffile), \"jedi_completions\", header, *completions)),\n                ]\n                print(\"evaluate-commands -client\", quote(client), quote(\"\\n\".join(cmds)))\n            ' | kak -p \"${kak_session}\"\n            rm -r ${dir}\n        ) & ) > /dev/null 2>&1 < /dev/null\n    }\n}\n\ndefine-command jedi-enable-autocomplete -docstring \"Add jedi completion candidates to the completer\" %{\n    set-option window completers option=jedi_completions %opt{completers}\n    hook window -group jedi-autocomplete InsertIdle .* %{ try %{\n        execute-keys -draft <a-h><a-k>\\..\\z<ret>\n        echo 'completing...'\n        jedi-complete\n    } }\n    alias window complete jedi-complete\n}\n\ndefine-command jedi-disable-autocomplete -docstring \"Disable jedi completion\" %{\n    set-option window completers %sh{ printf %s\\\\n \"'${kak_opt_completers}'\" | sed -e 's/option=jedi_completions://g' }\n    remove-hooks window jedi-autocomplete\n    unalias window complete jedi-complete\n}\n\n}\n"
  },
  {
    "path": "rc/tools/rust/racer.kak",
    "content": "hook -once global BufSetOption filetype=rust %{\n    require-module racer\n}\n\nprovide-module racer %{\n\ndeclare-option -hidden str racer_tmp_dir\ndeclare-option -hidden completions racer_completions\n\ndefine-command racer-complete -docstring \"Complete the current selection with racer\" %{\n    evaluate-commands %sh{\n        dir=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-racer.XXXXXXXX)\n        printf %s\\\\n \"set-option buffer racer_tmp_dir ${dir}\"\n        printf %s\\\\n \"evaluate-commands -no-hooks %{ write ${dir}/buf }\"\n    }\n    evaluate-commands %sh{\n        dir=${kak_opt_racer_tmp_dir}\n        (\n            trap - INT QUIT\n            cursor=\"${kak_cursor_line} $((${kak_cursor_column} - 1))\"\n            racer_data=$(racer --interface tab-text complete-with-snippet ${cursor} ${kak_buffile} ${dir}/buf)\n            compl=$(printf %s\\\\n \"${racer_data}\" | awk '\n                BEGIN { FS = \"\\t\"; ORS = \" \" }\n                /^PREFIX/ {\n                    column = ENVIRON[\"kak_cursor_column\"] + $2 - $3\n                    print ENVIRON[\"kak_cursor_line\"] \".\" column \"@@\" ENVIRON[\"kak_timestamp\"]\n                }\n                /^MATCH/ {\n                    word = $2\n                    desc = substr($9, 2, length($9) - 2)\n                    gsub(/\\|/, \"\\\\|\", desc)\n                    gsub(/\\\\n/, \"\\n\", desc)\n                    gsub(/!/, \"!!\", desc)\n                    info = $8\n                    gsub(/\\|/, \"\\\\|\", info)\n\n                    candidate = word \"|info -style menu %!\" desc \"!|\" word \" {MenuInfo}\" info\n\n                    gsub(/@/, \"@@\", candidate)\n                    gsub(/~/, \"~~\", candidate)\n                    print \"%~\" candidate \"~\"\n                }'\n            )\n            printf %s\\\\n \"evaluate-commands -client '${kak_client}' %@ set-option 'buffer=${kak_bufname}' racer_completions ${compl%?} @\" | kak -p ${kak_session}\n            rm -r ${dir}\n        ) > /dev/null 2>&1 < /dev/null &\n    }\n}\n\ndefine-command racer-enable-autocomplete -docstring \"Add racer completion candidates to the completer\" %{\n    set-option window completers option=racer_completions %opt{completers}\n    hook window -group racer-autocomplete InsertIdle .* %{ try %{\n        execute-keys -draft <a-h><a-k>([\\w\\.]|::).\\z<ret>\n        racer-complete\n    } }\n    alias window complete racer-complete\n}\n\ndefine-command racer-disable-autocomplete -docstring \"Disable racer completion\" %{\n    evaluate-commands %sh{ printf \"set-option window completers %s\\n\" $(printf %s \"${kak_opt_completers}\" | sed -e \"s/'option=racer_completions'//g\") }\n    remove-hooks window racer-autocomplete\n    unalias window complete racer-complete\n}\n\ndefine-command racer-go-definition -docstring \"Jump to where the rust identifier below the cursor is defined\" %{\n    evaluate-commands %sh{\n        dir=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-racer.XXXXXXXX)\n        printf %s\\\\n \"set-option buffer racer_tmp_dir ${dir}\"\n        printf %s\\\\n \"evaluate-commands -no-hooks %{ write ${dir}/buf }\"\n    }\n    evaluate-commands %sh{\n        dir=${kak_opt_racer_tmp_dir}\n        cursor=\"${kak_cursor_line} $((${kak_cursor_column} - 1))\"\n        racer_data=$(racer --interface tab-text  find-definition ${cursor} \"${kak_buffile}\" \"${dir}/buf\" | head -n 1)\n\n        racer_match=$(printf %s\\\\n \"$racer_data\" | cut -f1 )\n        if [ \"$racer_match\" = \"MATCH\" ]; then\n          racer_line=$(printf %s\\\\n \"$racer_data\" | cut -f3 )\n          racer_column=$(printf %s\\\\n \"$racer_data\" | cut -f4 )\n          racer_file=$(printf %s\\\\n \"$racer_data\" | cut -f5 )\n          printf %s\\\\n \"edit -existing '$racer_file' $racer_line $racer_column\"\n          case ${racer_file} in\n            \"${RUST_SRC_PATH}\"* | \"${CARGO_HOME:-$HOME/.cargo}\"/registry/src/*)\n              printf %s\\\\n \"set-option buffer readonly true\";;\n          esac\n        else\n          printf %s\\\\n \"echo -debug 'racer could not find a definition'\"\n        fi\n    }\n}\n\ndefine-command racer-show-doc -docstring \"Show the documentation about the rust identifier below the cursor\" %{\n    evaluate-commands %sh{\n        dir=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-racer.XXXXXXXX)\n        printf %s\\\\n \"set-option buffer racer_tmp_dir ${dir}\"\n        printf %s\\\\n \"evaluate-commands -no-hooks %{ write ${dir}/buf }\"\n    }\n    evaluate-commands %sh{\n        dir=${kak_opt_racer_tmp_dir}\n        cursor=\"${kak_cursor_line} ${kak_cursor_column}\"\n        racer_data=$(racer --interface tab-text  complete-with-snippet  ${cursor} \"${kak_buffile}\" \"${dir}/buf\" | sed -n 2p )\n        racer_match=$(printf %s\\\\n \"$racer_data\" | cut -f1)\n        if [ \"$racer_match\" = \"MATCH\" ]; then\n          racer_doc=$(\n            printf %s\\\\n \"$racer_data\" |\n            cut -f9  |\n            sed -e '\n\n              # Remove leading and trailing quotes\n              s/^\"\\(.*\\)\"$/\\1/g\n\n              # Escape all @ so that it can be properly used in the string expansion\n              s/@/\\\\@/g\n\n            ')\n          printf \"info %%@$racer_doc@\"\n        else\n          printf %s\\\\n \"echo -debug 'racer could not find a definition'\"\n        fi\n    }\n}\n\n}\n"
  },
  {
    "path": "rc/tools/spell.kak",
    "content": "declare-option -hidden range-specs spell_regions\ndeclare-option -hidden str spell_last_lang\n\ndeclare-option -docstring \"default language to use when none is passed to the spell-check command\" str spell_lang\n\ndefine-command -params ..1 -docstring %{\n    spell [<language>]: spell check the current buffer\n\n    The first optional argument is the language against which the check will be performed (overrides `spell_lang`)\n    Formats of language supported:\n      - ISO language code, e.g. 'en'\n      - language code above followed by a dash or underscore with an ISO country code, e.g. 'en-US'\n    } spell %{\n    try %{ add-highlighter window/ ranges 'spell_regions' }\n    evaluate-commands %sh{\n        use_lang() {\n            if ! printf %s \"$1\" | grep -qE '^[a-z]{2,3}([_-][A-Z]{2})?$'; then\n                echo \"fail 'Invalid language code (examples of expected format: en, en_US, en-US)'\"\n                exit 1\n            else\n                options=\"-l '$1'\"\n                printf 'set-option buffer spell_last_lang %s\\n' \"$1\"\n            fi\n        }\n\n        if [ $# -ge 1 ]; then\n            use_lang \"$1\"\n        elif [ -n \"${kak_opt_spell_lang}\" ]; then\n            use_lang \"${kak_opt_spell_lang}\"\n        fi\n\n        printf 'eval -no-hooks write %s\\n' \"${kak_response_fifo}\" > $kak_command_fifo\n\n        {\n            trap - INT QUIT\n            sed 's/^/^/' | eval \"aspell --byte-offsets -a $options\" 2>&1 | awk '\n                BEGIN {\n                    line_num = 1\n                    regions = ENVIRON[\"kak_timestamp\"]\n                    server_command = sprintf(\"kak -p \\\"%s\\\"\", ENVIRON[\"kak_session\"])\n                }\n\n                {\n                    if (/^@\\(#\\)/) {\n                        # drop the identification message\n                    }\n\n                    else if (/^\\*/) {\n                        # nothing\n                    }\n\n                    else if (/^[+-]/) {\n                        # required to ignore undocumented aspell functionality\n                    }\n\n                    else if (/^$/) {\n                        line_num++\n                    }\n\n                    else if (/^[#&]/) {\n                        word_len = length($2)\n                        word_pos = substr($0, 1, 1) == \"&\" ? substr($4, 1, length($4) - 1) : $3;\n                        regions = regions \" \" line_num \".\" word_pos \"+\" word_len \"|DiagnosticError\"\n                    }\n\n                    else {\n                        line = $0\n                        gsub(/\"/, \"&&\", line)\n                        command = \"fail \\\"\" line \"\\\"\"\n                        exit\n                    }\n                }\n\n                END {\n                    if (!length(command))\n                        command = \"set-option \\\"buffer=\" ENVIRON[\"kak_bufname\"] \"\\\" spell_regions \" regions\n\n                    print command | server_command\n                    close(server_command)\n                }\n            '\n        } <$kak_response_fifo >/dev/null 2>&1 &\n    }\n}\n\ndefine-command spell-clear %{\n    unset-option buffer spell_regions\n}\n\ndefine-command spell-rel-jump -hidden -params 0..1 %{ evaluate-commands %sh{\n    spell_first=\"${kak_opt_spell_regions%%|*}\"\n    spell_first=\"${spell_first#* }\"\n    spell_last=\"${kak_opt_spell_regions##* }\"\n    spell_last=\"${spell_last%|*}\"\n\n    # Make sure properly formatted selection descriptions are in `%opt{spell_regions}`\n    if ! printf %s \"${spell_first}\" | grep -qE '^[0-9]+\\.[0-9]+,[0-9]+\\.[0-9]+$'; then\n        exit\n    fi\n\n    get_prev=0\n    if [ \"$1\" = \"-rev\" ]; then\n        get_prev=1\n    elif [ -n \"$1\" ]; then\n        echo \"fail -- Unrecognised parameter $(kakquote \"$1\")\"\n    fi\n\n    printf %s \"${kak_opt_spell_regions#* }\" | \\\n        awk -v spell_first=\"${spell_first}\" -v spell_last=\"${spell_last}\" \\\n            -v get_prev=\"${get_prev}\" -F '[.,|]' -v RS=\" \" '\n            BEGIN {\n                split(ENVIRON[\"kak_selection_desc\"], sel)\n                cursor_row = sel[3]\n                cursor_col = sel[4]\n                # reverse order so cursor can be at beginning for spell-prev\n                split(spell_last, tmp)\n                spell_last = tmp[3] \".\" tmp[4] \",\" tmp[1] \".\" tmp[2]\n            }\n\n            {\n                # $1 - $4 is candidate row.col,row.col\n                check_row = $1 > cursor_row\n                check_col = $1 == cursor_row && (get_prev ? $2 >= cursor_col : $2 > cursor_col)\n\n                if (check_row || check_col) {\n                    next_spell = $1 \".\" $2 \",\" $3 \".\" $4\n                    exit\n                }\n                prev_spell = $3 \".\" $4 \",\" $1 \".\" $2\n            }\n\n            END {\n                target_spell = get_prev ? prev_spell : next_spell\n                if (!target_spell)\n                    target_spell = get_prev ? spell_last : spell_first\n\n                printf \"select %s\\n\", target_spell\n            }\n        '\n} }\n\ndefine-command spell-next %{ spell-rel-jump }\n\ndefine-command spell-prev %{ spell-rel-jump -rev }\n\ndefine-command \\\n    -docstring \"Suggest replacement words for the current selection, against the last language used by the spell-check command\" \\\n    spell-replace %{\n    prompt \\\n        -shell-script-candidates %{\n            options=\"\"\n            if [ -n \"$kak_opt_spell_last_lang\" ]; then\n                options=\"-l '$kak_opt_spell_last_lang'\"\n            fi\n            printf %s \"$kak_selection\" |\n                eval \"aspell -a $options\" |\n                sed -n -e '/^&/ { s/^[^:]*: //; s/, /\\n/g; p;}'\n        } \\\n        \"Replace with: \" \\\n        %{\n            evaluate-commands -save-regs a %{\n                set-register a %val{text}\n                execute-keys c <c-r>a <esc>\n            }\n        }\n}\n\n\ndefine-command -params 0.. \\\n    -docstring \"Add the current selection to the dictionary\" \\\n    spell-add %{ evaluate-commands %sh{\n    options=\"\"\n    if [ -n \"$kak_opt_spell_last_lang\" ]; then\n        options=\"-l '$kak_opt_spell_last_lang'\"\n    fi\n    if [ $# -eq 0 ]; then\n        # use selections\n        eval set -- \"$kak_quoted_selections\"\n    fi\n    while [ $# -gt 0 ]; do\n        word=\"$1\"\n        if ! printf '*%s\\n#\\n' \"${word}\" | eval \"aspell -a $options\" >/dev/null; then\n           printf 'fail \"Unable to add word: %s\"' \"$(printf %s \"${word}\" | sed 's/\"/&&/g')\"\n           exit 1\n        fi\n        shift\n    done\n}}\n"
  },
  {
    "path": "rc/windowing/appleterminal.kak",
    "content": "# macOS Terminal.app\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nprovide-module appleterminal %{\n\n# ensure that we're running in Terminal.app\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || [ \"$TERM_PROGRAM\" = \"Apple_Terminal\" ] || echo 'fail Terminal.app not detected'\n}\n\ndefine-command appleterminal-terminal-window -params 1.. -docstring '\nappleterminal-terminal-window <program> [<arguments>]: create a new terminal as a Terminal.app window\nThe program passed as argument will be executed in the new terminal'\\\n%{\n    nop %sh{\n        # join the arguments as one string for the shell execution (see x11.kak)\n        quoted=$(\n            for i in exec env \"PATH=$PATH\" \"TMPDIR=$TMPDIR\" \"$@\"; do\n                printf \"'%s' \" \"$(printf %s \"$i\" | sed \"s|'|'\\\\\\\\''|g\")\"\n            done\n        )\n\n        # since Terminal.app runs the command in the interactive shell, add initial space to prevent adding it to shell history\n        cmd=\" $quoted\"\n        echo \"$cmd\" | osascript                             \\\n            -e 'set s to (do shell script \"cat 0<&3\")'      \\\n            -e 'tell application \"Terminal\" to do script s' >/dev/null 3<&0\n    }\n}\ncomplete-command appleterminal-terminal-window shell\n\nalias global terminal appleterminal-terminal-window\n\ndefine-command appleterminal-focus -params ..1 -docstring '\nappleterminal-focus [<client>]: focus the given client\nIf no client is passed then the current one is used' \\\n%{\n    fail 'Focusing Terminal.app client is unimplemented'\n}\ncomplete-command -menu appleterminal-focus client\n\nalias global focus appleterminal-focus\n\n}\n"
  },
  {
    "path": "rc/windowing/detection.kak",
    "content": "# Attempt to detect the windowing environment we're operating in\n#\n# We try to load modules from the windowing_modules str-list option in order,\n# stopping when one of the modules loads successfully. This ensures that only\n# a single module is loaded by default.\n#\n# On load each module must attempt to detect the environment it's appropriate\n# for, and if the environment isn't appropriate it must fail with an error.\n# In addition, each module must check for the length of the windowing_modules\n# str-list option defined below, and must /not/ check for an appropriate\n# environment if the list is empty. An example of this test:\n#\n# evaluate-commands %sh{\n#     [ -z \"${kak_opt_windowing_modules}\" ] || [ -n \"$TMUX\" ] || echo 'fail tmux not detected'\n# }\n#\n# Each module is expected to define at least two aliases:\n#  * terminal - create a new terminal with sensible defaults\n#  * focus - focus the specified client, defaulting to the current client\n#\n\ndeclare-option -docstring \\\n\"Ordered list of windowing modules to try and load. An empty list disables\nboth automatic module loading and environment detection, enabling complete\nmanual control of the module loading.\" \\\nstr-list windowing_modules 'tmux' 'screen' 'zellij' 'wezterm' 'kitty' 'iterm' 'appleterminal' 'sway' 'niri' 'hyprland' 'wayland' 'x11'\n\ndeclare-option -docstring %{\n    windowing module to use in the 'terminal' command\n} str windowing_module\n\ndeclare-option -docstring %{\n    where to create new windows in the 'terminal' command.\n\n    Possible values:\n    - \"window\" (default) - new window\n    - \"horizontal\" - horizontal split (left/right)\n    - \"vertical\" - vertical split (top/bottom)\n    - \"tab\" - new tab besides current window\n} str windowing_placement window\n\ndefine-command terminal -params 1.. -docstring %{\n    terminal <program> [<arguments>]: create a new terminal using the preferred windowing environment and placement\n\n    This executes \"%opt{windowing_module}-terminal-%opt{windowing_placement}\" with the given arguments.\n    If the windowing module is 'wayland', 'sway' or 'x11', then the 'termcmd' option is used as terminal program.\n\n    Example usage:\n\n        terminal sh\n        evaluate-commands %{ set local windowing_placement horizontal; terminal sh }\n\n    See also the 'new' command.\n} %{\n    \"%opt{windowing_module}-terminal-%opt{windowing_placement}\" %arg{@}\n}\ncomplete-command terminal shell\n\nhook -group windowing global KakBegin .* %{\n    evaluate-commands %sh{\n        set -- ${kak_opt_windowing_modules}\n        if [ $# -gt 0 ]; then\n            echo 'try %{ '\n            while [ $# -ge 1 ]; do\n                echo \"require-module ${1}; set-option global windowing_module ${1} } catch %{ \"\n                shift\n            done\n            echo \"echo -debug 'no windowing module detected' }\"\n        fi\n    }\n}\n"
  },
  {
    "path": "rc/windowing/hyprland.kak",
    "content": "# https://hypr.land\nprovide-module hyprland %{\n\n# Ensure we're actually in Hyprland\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] ||\n    [ \"$XDG_CURRENT_DESKTOP\"  = \"Hyprland\" ] &&\n    [ -n \"$HYPRLAND_INSTANCE_SIGNATURE\" ] ||\n    echo 'fail hyprland not detected'\n}\n\nrequire-module 'wayland'\n\nalias global hyprland-terminal-window wayland-terminal-window\n\ndefine-command hyprland-focus-pid -hidden %{\n    evaluate-commands %sh{\n        pid=$kak_client_pid\n        while [ \"$(hyprctl dispatch focuswindow pid:$pid)\" != \"ok\" ]; do\n            pid=$(ps -p $pid -o ppid= | tr -d \" \")\n            if [ -z $pid ] || [ $pid -le 1 ]; then\n                echo \"fail Can't find PID for Sway window to focus\"\n                break\n            fi\n        done\n    }\n}\n\ndefine-command hyprland-focus -params ..1 -docstring '\nhyprland-focus [<kakoune_client>]: focus a given client''s window.\nIf no client is passed, then the current client is used' \\\n%{\n    evaluate-commands %sh{\n        if [ $# -eq 1 ]; then\n            printf \"evaluate-commands -client '%s' hyprland-focus-pid\" \"$1\"\n        else\n            echo hyprland-focus-pid\n        fi\n    }\n}\ncomplete-command -menu hyprland-focus client\n\nalias global focus hyprland-focus\n\n}\n"
  },
  {
    "path": "rc/windowing/iterm.kak",
    "content": "# https://www.iterm2.com\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nprovide-module iterm %{\n\n# ensure that we're running on iTerm\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || [ \"$TERM_PROGRAM\" = \"iTerm.app\" ] || echo 'fail iTerm not detected'\n}\n\ndefine-command -hidden -params 2.. iterm-terminal-impl %{\n    nop %sh{\n        direction=\"$1\"\n        shift\n        # join the arguments as one string for the shell execution (see x11.kak)\n        args=$(\n            for i in \"$@\"; do\n                printf \"'%s' \" \"$(printf %s \"$i\" | sed \"s|'|'\\\\\\\\''|g\")\"\n            done\n        )\n\n        # go through another round of escaping for osascript\n        # \\ -> \\\\\n        # \" -> \\\"\n        do_esc() {\n            printf %s \"$*\" | sed -e 's|\\\\|\\\\\\\\|g; s|\"|\\\\\"|g'\n        }\n\n        escaped=$(do_esc \"$args\")\n        esc_path=$(do_esc \"$PATH\")\n        esc_tmp=$(do_esc \"$TMPDIR\")\n        cmd=\"env PATH='${esc_path}' TMPDIR='${esc_tmp}' $escaped\"\n        if [ \"$direction\" = 'tab' ]; then\n            osascript                                                       \\\n            -e \"tell application \\\"iTerm\\\"\"                                 \\\n            -e \"    tell current window\"                                    \\\n            -e \"        create tab with default profile command \\\"${cmd}\\\"\" \\\n            -e \"    end tell\"                                               \\\n            -e \"end tell\" >/dev/null\n        elif [ \"$direction\" = 'window' ]; then\n            osascript                                                      \\\n            -e \"tell application \\\"iTerm\\\"\"                                \\\n            -e \"    create window with default profile command \\\"${cmd}\\\"\" \\\n            -e \"end tell\" >/dev/null\n        else\n            osascript                                                                             \\\n            -e \"tell application \\\"iTerm\\\"\"                                                       \\\n            -e \"    tell current session of current window\"                                       \\\n            -e \"        tell (split ${direction} with same profile command \\\"${cmd}\\\") to select\" \\\n            -e \"    end tell\"                                                                     \\\n            -e \"end tell\" >/dev/null\n        fi\n    }\n}\n\ndefine-command iterm-terminal-vertical -params 1.. -docstring '\niterm-terminal-vertical <program> [<arguments>]: create a new terminal as an iterm pane\nThe current pane is split into two, left and right\nThe program passed as argument will be executed in the new terminal'\\\n%{\n    iterm-terminal-impl 'vertically' %arg{@}\n}\ncomplete-command iterm-terminal-vertical shell\n\ndefine-command iterm-terminal-horizontal -params 1.. -docstring '\niterm-terminal-horizontal <program> [<arguments>]: create a new terminal as an iterm pane\nThe current pane is split into two, top and bottom\nThe program passed as argument will be executed in the new terminal'\\\n%{\n    iterm-terminal-impl 'horizontally' %arg{@}\n}\ncomplete-command iterm-terminal-horizontal shell\n\ndefine-command iterm-terminal-tab -params 1.. -docstring '\niterm-terminal-tab <program> [<arguments>]: create a new terminal as an iterm tab\nThe program passed as argument will be executed in the new terminal'\\\n%{\n    iterm-terminal-impl 'tab' %arg{@}\n}\ncomplete-command iterm-terminal-tab shell\n\ndefine-command iterm-terminal-window -params 1.. -docstring '\niterm-terminal-window <program> [<arguments>]: create a new terminal as an iterm window\nThe program passed as argument will be executed in the new terminal'\\\n%{\n    iterm-terminal-impl 'window' %arg{@}\n}\ncomplete-command iterm-terminal-window shell\n\ndefine-command iterm-focus -params ..1 -docstring '\niterm-focus [<client>]: focus the given client\nIf no client is passed then the current one is used' \\\n%{\n    evaluate-commands %sh{\n        if [ $# -eq 1 ]; then\n            printf %s\\\\n \"evaluate-commands -client '$1' focus\"\n        else\n            session=\"${kak_client_env_ITERM_SESSION_ID#*:}\"\n            osascript                                                      \\\n            -e \"tell application \\\"iTerm\\\" to repeat with aWin in windows\" \\\n            -e \"    tell aWin to repeat with aTab in tabs\"                 \\\n            -e \"        tell aTab to repeat with aSession in sessions\"     \\\n            -e \"            tell aSession\"                                 \\\n            -e \"                if (unique id = \\\"${session}\\\") then\"      \\\n            -e \"                    tell aWin\"                             \\\n            -e \"                        select\"                            \\\n            -e \"                    end tell\"                              \\\n            -e \"                    tell aTab\"                             \\\n            -e \"                        select\"                            \\\n            -e \"                    end tell\"                              \\\n            -e \"                    select\"                                \\\n            -e \"                end if\"                                    \\\n            -e \"            end tell\"                                      \\\n            -e \"        end repeat\"                                        \\\n            -e \"    end repeat\"                                            \\\n            -e \"end repeat\"\n        fi\n    }\n}\ncomplete-command -menu iterm-focus client\n\nalias global focus iterm-focus\n\n}\n"
  },
  {
    "path": "rc/windowing/kitty.kak",
    "content": "# https://sw.kovidgoyal.net/kitty/index.html\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nprovide-module kitty %{\n\n# ensure that we're running on kitty\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || [ \"$TERM\" = \"xterm-kitty\" ] || echo 'fail Kitty not detected'\n}\n\ndeclare-option -docstring %{window type that kitty creates on new and repl calls (window|os-window)} str kitty_window_type window\n\ndefine-command kitty-terminal-window -params 1.. -docstring '\nkitty-terminal-window <program> [<arguments>]: create a new terminal as a kitty window\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    nop %sh{\n        match=\"\"\n        if [ -n \"$kak_client_env_KITTY_WINDOW_ID\" ]; then\n            match=\"--match=window_id:$kak_client_env_KITTY_WINDOW_ID\"\n        fi\n\n        listen=\"\"\n        if [ -n \"$kak_client_env_KITTY_LISTEN_ON\" ]; then\n            listen=\"--to=$kak_client_env_KITTY_LISTEN_ON\"\n        fi\n\n        kitty @ $listen launch --no-response --type=\"$kak_opt_kitty_window_type\" --cwd=\"$PWD\" $match \"$@\"\n    }\n}\ncomplete-command kitty-terminal-window shell\n\ndefine-command kitty-terminal-tab -params 1.. -docstring '\nkitty-terminal-tab <program> [<arguments>]: create a new terminal as kitty tab\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    nop %sh{\n        match=\"\"\n        if [ -n \"$kak_client_env_KITTY_WINDOW_ID\" ]; then\n            match=\"--match=window_id:$kak_client_env_KITTY_WINDOW_ID\"\n        fi\n\n        listen=\"\"\n        if [ -n \"$kak_client_env_KITTY_LISTEN_ON\" ]; then\n            listen=\"--to=$kak_client_env_KITTY_LISTEN_ON\"\n        fi\n\n        kitty @ $listen launch --no-response --type=tab --cwd=\"$PWD\" $match \"$@\"\n    }\n}\ncomplete-command kitty-terminal-tab shell\n\ndefine-command kitty-focus -params ..1 -docstring '\nkitty-focus [<client>]: focus the given client\nIf no client is passed then the current one is used' \\\n%{\n    evaluate-commands %sh{\n        if [ $# -eq 1 ]; then\n            printf \"evaluate-commands -client '%s' focus\" \"$1\"\n        else\n            match=\"\"\n            if [ -n \"$kak_client_env_KITTY_WINDOW_ID\" ]; then\n                match=\"--match=id:$kak_client_env_KITTY_WINDOW_ID\"\n            fi\n\n            listen=\"\"\n            if [ -n \"$kak_client_env_KITTY_LISTEN_ON\" ]; then\n                listen=\"--to=$kak_client_env_KITTY_LISTEN_ON\"\n            fi\n\n            kitty @ $listen focus-window --no-response $match\n        fi\n    }\n}\ncomplete-command -menu kitty-focus client\n\nalias global focus kitty-focus\n\n# deprecated\ndefine-command -hidden kitty-terminal -params 1.. %{\n    kitty-terminal-window %arg{@}\n}\n\n}\n"
  },
  {
    "path": "rc/windowing/new-client.kak",
    "content": "define-command new -params .. -docstring '\nnew [<commands>]: create a new Kakoune client\nThe ''terminal'' command is used to determine the user''s preferred terminal emulator\nThe optional arguments are passed as commands to the new client' \\\n%{\n    terminal kak -c %val{session} -e \"%arg{@}\"\n}\n\ncomplete-command -menu new command\n"
  },
  {
    "path": "rc/windowing/niri.kak",
    "content": "# https://yalter.github.io/niri/\n# ------------------------------\n\n# Kakoune Niri windowing module\n\n# Niri is a scrolling window manager;\n# vertical/horizontal splits aren't really a native concept.\n# A workspace consists of columns of arbitrary width, where each\n# column can have one or more window in it. Windows in a column\n# are stacked on top of each other. To \"consume\" a window into a\n# column is to pull it BELOW the other windows in the column.\n# For example, consider:\n# +-----+ +-----+                         +-----+\n# |     | |     |                         |  A  |\n# |  A  | |  B  |  --consume B into A-->  +-----+\n# |     | |     |                         |  B  |\n# +-----+ +-----+                         +-----+\n\n# We provide three ways to open a new window.\n\n# normal: simply spawn a new window to the right.\n#   This is the default for new windows in Niri.\n# Alias: horizontal\n# +-----+ +-----+\n# |     | |     |\n# | KAK | | NEW |\n# |     | |     |\n# +-----+ +-----+\n\n# consume: the new window is consumed into the current column.\n# Alias: vertical\n# +-----+\n# | KAK |\n# +-----+\n# | NEW |\n# +-----+\n\n# consume-right: spawn new window and consume into column to the right\n#   Note: FOO is an already open window to the right.\n# Alias: none\n# +-----+ +-----+\n# |     | | FOO |\n# | KAK | +-----+\n# |     | | NEW |\n# +-----+ +-----+\n\n# In terms of closest analogues to the vertical & horizontal split paradigm,\n# we alias \"vertical\" to \"consume\" and we alias \"horizontal\" to \"normal\".\n# There isn't a clean alias for \"consume-right\", but it's a convenient command\n# that users can invoke directly.\n\nprovide-module niri %~\n\n# ensure we're actually in niri\n# and that another module isn't already loaded\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || \n    [ -n \"$NIRI_SOCKET\" ] ||\n    echo 'fail NIRI_SOCKET is not set'\n}\n\n# load wayland module\nrequire-module wayland\n\nalias global niri-terminal-window wayland-terminal-window\n\n# first arg MUST be left|right, rest are passed to program in new terminal\n# Direction is relative to the new window, which is always opened to the right.\n# Therefore, left means consume into current column.\ndefine-command -hidden niri-terminal-consume-impl -params 1.. %{\n    # When Niri creates a new window, it's always in a new column\n    # to the right of the focused window, and the new window is focused.\n    # Niri doesn't allow us to specify where the new window should go,\n    # we can only operate on it AFTER it's been spawned.\n    # The idea to spawn a terminal, and then consume it\n    # either into the current col, or the col to the right.\n    # However, after spawning the terminal, we can't safely\n    # operate on it immediately because it takes some time (aprx. 0.1s)\n    # to actually initialize.\n\n    # 1.  Open niri's event stream, wait for it to initialize.\n    #       Note: event stream is newline-delimited JSON.\n    # 2.  Execute command to open new terminal window.\n    # 3.  Watch the event stream and wait for window to be spawned.\n    # 3.a Only operate on window if not floating.\n    # 3.b Switch focus to new window if not already focused.\n    #     ^ (Not certain this is necessary, but better to be safe).\n    # 4.  Once the window exists, consume in specified direction.\n    nop %sh{\n        # First arg always left|right. No need for guards b/c this is private function.\n        direction=$1\n        shift\n        # create fifo for collecting event stream\n        temp=$(mktemp -d kak-niri-XXXXXX)\n        fifo=\"$temp/fifo\"\n        mkfifo \"$fifo\"\n        # register func to cleanup fifo\n        cleanup() {\n        \trm -rf \"$temp\"\n        }\n        trap cleanup EXIT INT TERM\n        # read event stream to fifo\n        niri msg -j event-stream >\"$fifo\" &\n        stream_pid=$!\n        # register timeout fallback\n        (\n        \tsleep 3\n        \tkill \"$!\" 2>/dev/null\n        \tkill \"$stream_pid\" 2>/dev/null\n        ) >/dev/null 2>&1 </dev/null &\n        watchdog_pid=$!\n\n        # safely quote arguments for passing to new terminal\n        kakquote() {\n        \tset -- \"$*\" \"\"\n        \twhile [ \"${1#*\\'}\" != \"$1\" ]; do\n        \t\tset -- \"${1#*\\'}\" \"$2${1%%\\'*}''\"\n        \tdone\n        \tprintf \"'%s' \" \"$2$1\"\n        }\n        # loop over args and quote all\n        quoted_args=''\n        for arg in \"$@\"; do\n        \tquoted_args=\"$quoted_args$(kakquote \"$arg\")\"\n        done\n        # run this block async to avoid user-facing lag\n        {\n            # track when new window is ready\n        \tready=false\n        \t# read lines of event stream from fifo\n        \t#   Note: each line is one event.\n        \t#   We wait for the event that indicates stream is initialized,\n        \t#   then we spawn the new terminal window. The next WindowOpenedOrChanged\n        \t#   event immediately after running niri-terminal-window SHOULD be\n        \t#   the event for this window, but it's not guaranteed. There is a very,\n        \t#   very tiny window for a race condition here. Unfortunately, I don't\n        \t#   believe we have a good way to handle it because there's no way to\n        \t#   know the window's ID before it's spawned. This may be a good job for\n        \t#   the Ostrich algorithm (https://en.wikipedia.org/wiki/Ostrich_algorithm)\n        \twhile IFS= read -r line; do\n        \t    # each line is a JSON object\n        \t\tcase \"$line\" in\n        \t\t# this line means stream is initialized\n        \t\t*\"OverviewOpenedOrClosed\"*)\n        \t\t    # guard to avoid spawning terminal twice\n        \t\t\tif [ \"$ready\" = false ]; then\n        \t\t\t\tready=true\n        \t\t\t\t# spawn terminal with command\n        \t\t\t\tprintf 'niri-terminal-window %s' \"$quoted_args\" >\"$kak_command_fifo\"\n        \t\t\tfi\n        \t\t\t;;\n        \t\t# newly opened window is now available to act on\n        \t\t*\"WindowOpenedOrChanged\"*)\n        \t\t    # only operate if window isn't floating\n        \t\t    # This accursed incantation is to access the JSON field\n        \t\t    # without depending on jq.\n        \t\t\tif expr \"${line}\" : '.*\"is_floating\":[[:space:]]*false.*' \\\n        \t\t\t\t>/dev/null; then\n        \t\t\t\t# handle new window being unfocused\n        \t\t\t\tif expr \"${line}\" : '.*\"is_focused\":[[:space:]]*false.*' \\\n        \t\t\t\t\t>/dev/null; then\n        \t\t\t\t\t# extract the window ID\n        \t\t\t\t\t# (POSIX compliant JSON parsing is ugly, sorry)\n        \t\t\t\t\tid=\"$(echo \"$line\" | sed -n 's/.*\"id\":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')\"\n        \t\t\t\t\t# focus the new window\n        \t\t\t\t\tniri msg action focus-window --id \"$id\"\n        \t\t\t\tfi\n        \t\t\t\t# consume the new window in specified direction\n        \t\t\t\tniri msg action consume-or-expel-window-${direction}\n        \t\t\tfi\n        \t\t\t# close the stream reader and timeout watchdog\n        \t\t\tkill \"$stream_pid\"\n        \t\t\twait \"$stream_pid\" 2>/dev/null\n        \t\t\tkill \"$watchdog_pid\" 2>/dev/null\n        \t\t\texit 0\n        \t\t\t;;\n        \t\tesac\n        \tdone <\"$fifo\"\n        } >/dev/null 2>&1 </dev/null &\n    }\n}\n\ndefine-command niri-terminal-consume -params 1.. -docstring '\n    niri-terminal-consume <program> [<arguments>]: create a new terminal as a Niri window and consume it into the current column\n    The program passed as argument will be executed in the new terminal' \\\n%{\n    niri-terminal-consume-impl left %arg{@}\n}\ncomplete-command niri-terminal-consume shell\n\ndefine-command niri-terminal-consume-right -params 1.. -docstring '\n    niri-terminal-consume-right <program> [<arguments>]: create a new terminal as a Niri window and consume it into the column to the right\n    The program passed as argument will be executed in the new terminal' \\\n%{\n    niri-terminal-consume-impl right %arg{@}\n}\ncomplete-command niri-terminal-consume-right shell\n\ndefine-command niri-new-consume -params .. -docstring '\nniri-new-consume [<commands>]: create a new Kakoune client and consume it into the current column\nThe optional arguments are passed as commands to the new client' \\\n%{\n    niri-terminal-consume kak -c %val{session} -e \"%arg{@}\"\n}\ncomplete-command -menu niri-new-consume command\n\ndefine-command niri-new-consume-right -params .. -docstring '\nniri-new-consume-right [<commands>]: create a new Kakoune client and consume it into the column on the right\nThe optional arguments are passed as commands to the new client' \\\n%{\n    niri-terminal-consume-right kak -c %val{session} -e \"%arg{@}\"\n}\ncomplete-command -menu niri-new-consume-right command\n\ndefine-command niri-new-window -params .. -docstring '\nniri-new-window [<commands>]: create a new Kakoune client\nThe ''niri-terminal-window'' command is used to determine the user''s preferred terminal emulator\nThe optional arguments are passed as commands to the new client' \\\n%{\n    niri-terminal-window kak -c %val{session} -e \"%arg{@}\"\n}\ncomplete-command -menu niri-new-window command\n\nalias global niri-terminal-vertical niri-terminal-consume\nalias global niri-terminal-horizontal niri-terminal-window\n\n~\n"
  },
  {
    "path": "rc/windowing/repl/dtach.kak",
    "content": "provide-module dtach-repl %{\n\n# test if dtach is installed\nevaluate-commands %sh{\n    [ -n \"$(command -v dtach)\" ] || echo 'fail dtach not found'\n}\n\ndeclare-option -docstring \"id of the REPL\" str dtach_repl_id\n\ndefine-command -docstring %{\n    dtach-repl [<arguments>]: create a new terminal window for repl interaction\n    All optional parameters are forwarded to the new terminal window\n} \\\n    -params .. \\\n    dtach-repl %{ terminal sh -c %{\n        file=\"$(mktemp -u -t kak_dtach_repl.XXXXX)\"\n        trap 'rm -f \"${file}\"' EXIT\n        printf \"evaluate-commands -try-client $1 \\\n            'set-option current dtach_repl_id ${file}'\" | kak -p \"$2\"\n        shift 2\n        dtach -c \"${file}\" -E sh -c \"${@:-$SHELL}\" || \"${@:-$SHELL}\"\n    } -- %val{client} %val{session} %arg{@}\n}\ncomplete-command dtach-repl shell\n\ndefine-command dtach-send-text -params 0..1 -docstring %{\n        dtach-send-text [text]: Send text to the REPL.\n        If no text is passed, then the selection is used\n        } %{\n    nop %sh{\n        printf \"%s\" \"${@:-$kak_selection}\" | dtach -p \"$kak_opt_dtach_repl_id\"\n    }\n}\n\nalias global repl-new dtach-repl\nalias global repl-send-text dtach-send-text\n\n}\n"
  },
  {
    "path": "rc/windowing/repl/kitty.kak",
    "content": "hook global ModuleLoaded kitty %{\n    require-module kitty-repl\n}\n\nprovide-module kitty-repl %{\n\ndefine-command -params .. \\\n    -docstring %{\n        kitty-repl [<arguments>]: Create a new window for repl interaction.\n\n        All optional parameters are forwarded to the new window.\n    } \\\n    kitty-repl %{\n    nop %sh{\n        if [ $# -eq 0 ]; then\n            cmd=\"${SHELL:-/bin/sh}\"\n        else\n            cmd=\"$*\"\n        fi\n\n       match=\"\"\n        if [ -n \"$kak_client_env_KITTY_WINDOW_ID\" ]; then\n            match=\"--match=window_id:$kak_client_env_KITTY_WINDOW_ID\"\n        fi\n\n        listen=\"\"\n        if [ -n \"$kak_client_env_KITTY_LISTEN_ON\" ]; then\n            listen=\"--to=$kak_client_env_KITTY_LISTEN_ON\"\n        fi\n\n        kitty @ $listen launch --no-response --keep-focus --type=\"$kak_opt_kitty_window_type\" --title=kak_repl_window --cwd=\"$PWD\" $match $cmd\n    }\n}\ncomplete-command kitty-repl shell\n\ndefine-command -hidden -params 0..1 \\\n    -docstring %{\n        kitty-send-text [text]: Send text to the REPL window.\n\n        If no text is passed, the selection is used.\n    } \\\n    kitty-send-text %{\n    nop %sh{\n        if [ $# -eq 0 ]; then\n            text=\"$kak_selection\"\n        else\n            text=\"$1\"\n        fi\n        kitty @ send-text --match=title:kak_repl_window \"$text\"\n    }\n}\n\nalias global repl-new kitty-repl\nalias global repl-send-text kitty-send-text\n\n}\n"
  },
  {
    "path": "rc/windowing/repl/tmux.kak",
    "content": "# http://tmux.github.io/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n# Tmux version >= 2 is required to use this module\n\nhook global ModuleLoaded tmux %{\n    require-module tmux-repl\n}\n\nprovide-module tmux-repl %{\n\ndeclare-option -docstring \"tmux pane id in which the REPL is running\" str tmux_repl_id\n\ndefine-command -hidden -params 1.. tmux-repl-impl %{\n    evaluate-commands %sh{\n        if [ -z \"$TMUX\" ]; then\n            echo 'fail This command is only available in a tmux session'\n            exit\n        fi\n        tmux_args=\"$1\"\n        if [ \"${1%%-*}\" = split ]; then\n            tmux_args=\"$tmux_args -t ${kak_client_env_TMUX_PANE}\"\n        elif [ \"${1%% *}\" = new-window ]; then\n            session_id=$(tmux display-message -p -t ${kak_client_env_TMUX_PANE} '#{session_id}')\n            tmux_args=\"$tmux_args -t $session_id\"\n        fi\n        shift\n        repl_pane_id=$(tmux $tmux_args -P -F '#{pane_id}' \"$@\")\n        printf \"set-option current tmux_repl_id '%s'\" \"$repl_pane_id\"\n    }\n}\n\ndefine-command tmux-repl-vertical -params 0.. -docstring \"Create a new vertical pane for repl interaction\" %{\n    tmux-repl-impl 'split-window -v' %arg{@}\n}\ncomplete-command tmux-repl-vertical shell\n\ndefine-command tmux-repl-horizontal -params 0.. -docstring \"Create a new horizontal pane for repl interaction\" %{\n    tmux-repl-impl 'split-window -h' %arg{@}\n}\ncomplete-command tmux-repl-horizontal shell\n\ndefine-command tmux-repl-window -params 0.. -docstring \"Create a new window for repl interaction\" %{\n    tmux-repl-impl 'new-window' %arg{@}\n}\ncomplete-command tmux-repl-window shell\n\ndefine-command -params 0..1 tmux-repl-set-pane -docstring %{\n        tmux-repl-set-pane [pane number]: Set an existing tmux pane for repl interaction\n        If the address of new pane is not given, next pane is used\n        (To get the pane number in tmux,\n        use 'tmux display-message -p '#{pane_id}'\" in that pane)\n    } %{\n    evaluate-commands %sh{\n        if [ -z \"$TMUX\" ]; then\n            echo 'fail This command is only available in a tmux session'\n            exit\n        fi\n        if [ $# -eq 0 ]; then\n            curr_pane_no=\"${kak_client_env_TMUX_PANE#%}\"\n            tgt_pane=$((curr_pane_no+1))\n        else\n            tgt_pane=\"$1\"\n        fi\n        curr_win=\"$(tmux display-message -t ${kak_client_env_TMUX_PANE} -p '#{window_id}')\" \n        if tmux list-panes -t \"$curr_win\" -F \\#D | grep -Fxq \"%\"$tgt_pane; then\n            printf \"set-option current tmux_repl_id '%s'\" %$tgt_pane\n        else\n            echo 'fail The correct pane is not there. Activate using tmux-terminal-* or some other way'\n        fi\n    }\n}\n\ndefine-command -hidden tmux-send-text -params 0..1 -docstring %{\n        tmux-send-text [text]: Send text to the REPL pane.\n        If no text is passed, then the selection is used\n    } %{\n    evaluate-commands %sh{\n        if [ $# -eq 0 ]; then\n            tmux set-buffer -b kak_selection -- \"${kak_selection}\"\n        else\n            tmux set-buffer -b kak_selection -- \"$1\"\n        fi\n        tmux paste-buffer -b kak_selection -t \"$kak_opt_tmux_repl_id\" ||\n        echo 'fail tmux-send-text: failed to send text, see *debug* buffer for details'\n    }\n}\n\nalias global repl-new tmux-repl-horizontal\nalias global repl-send-text tmux-send-text\n\n}\n"
  },
  {
    "path": "rc/windowing/repl/x11.kak",
    "content": "hook global ModuleLoaded x11 %{\n    require-module x11-repl\n}\n\nprovide-module x11-repl %{\n\ndeclare-option -docstring \"window id of the REPL window\" str x11_repl_id\n\ndefine-command -docstring %{\n    x11-repl [<arguments>]: create a new window for repl interaction\n    All optional parameters are forwarded to the new window\n} \\\n    -params .. \\\n    x11-repl %{ x11-terminal-window sh -c %{\n        winid=\"${WINDOWID:-$(xdotool search --pid ${PPID} | tail -1)}\"\n        printf \"evaluate-commands -try-client $1 \\\n            'set-option current x11_repl_id ${winid}'\" | kak -p \"$2\"\n        shift 2;\n        [ \"$1\" ] && \"$@\" || \"$SHELL\"\n    } -- %val{client} %val{session} %arg{@}\n}\ncomplete-command x11-repl shell\n\ndefine-command x11-send-text -params 0..1 -docstring %{\n        x11-send-text [text]: Send text to the REPL window.\n        If no text is passed, then the selection is used\n        } %{\n    evaluate-commands %sh{\n        ([ \"$#\" -gt 0 ] && printf \"%s\" \"$1\" || printf \"%s\" \"${kak_selection}\" ) | xsel -i ||\n        echo 'fail x11-send-text: failed to run xsel, see *debug* buffer for details' &&\n        kak_winid=$(xdotool getactivewindow) &&\n        xdotool windowactivate \"${kak_opt_x11_repl_id}\" key --clearmodifiers Shift+Insert &&\n        xdotool windowactivate \"${kak_winid}\" ||\n        echo 'fail x11-send-text: failed to run xdotool, see *debug* buffer for details'\n    }\n}\n\nalias global repl-new x11-repl\nalias global repl-send-text x11-send-text\n\n}\n"
  },
  {
    "path": "rc/windowing/screen.kak",
    "content": "# http://gnu.org/software/screen/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n\nprovide-module screen %{\n\n# ensure that we're running under screen\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || [ -n \"$STY\" ] || echo 'fail screen not detected'\n}\n\ndefine-command screen-terminal-impl -hidden -params 3.. %{\n    nop %sh{\n        tty=\"$(ps -o tty ${kak_client_pid} | tail -n 1)\"\n        screen -X eval \"$1\" \"$2\"\n        shift 2\n        # see x11.kak for what this achieves\n        args=$(\n            for i in \"$@\"; do\n                printf \"'%s' \" \"$(printf %s \"$i\" | sed \"s|'|'\\\\\\\\''|g\")\"\n            done\n        )\n        screen -X screen sh -c \"${args} ; screen -X remove\" < \"/dev/$tty\"\n    }\n}\n\ndefine-command screen-terminal-vertical -params 1.. -docstring '\nscreen-terminal-vertical <program> [<arguments>]: create a new terminal as a screen pane\nThe current pane is split into two, left and right\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    screen-terminal-impl 'split -v' 'focus right' %arg{@}\n}\ncomplete-command screen-terminal-vertical shell\n\ndefine-command screen-terminal-horizontal -params 1.. -docstring '\nscreen-terminal-horizontal <program> [<arguments>]: create a new terminal as a screen pane\nThe current pane is split into two, top and bottom\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    screen-terminal-impl 'split -h' 'focus down' %arg{@}\n}\ncomplete-command screen-terminal-horizontal shell\n\ndefine-command screen-terminal-window -params 1.. -docstring '\nscreen-terminal-window <program> [<arguments>]: create a new terminal as a screen window\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    nop %sh{\n        tty=\"$(ps -o tty ${kak_client_pid} | tail -n 1)\"\n        screen -X screen \"$@\" < \"/dev/$tty\"\n    }\n}\ncomplete-command screen-terminal-window shell\n\ndefine-command screen-focus -params ..1 -docstring '\nscreen-focus [<client>]: focus the given client\nIf no client is passed then the current one is used' \\\n%{\n    evaluate-commands %sh{\n        if [ $# -eq 1 ]; then\n            printf %s\\\\n \"\n                evaluate-commands -client '$1' focus\n                \"\n        elif [ -n \"${kak_client_env_STY}\" ]; then\n            tty=\"$(ps -o tty ${kak_client_pid} | tail -n 1)\"\n            screen -X select \"${kak_client_env_WINDOW}\" < \"/dev/$tty\"\n        fi\n    }\n}\ncomplete-command -menu screen-focus client \n\nalias global focus screen-focus\n\n}\n"
  },
  {
    "path": "rc/windowing/sway.kak",
    "content": "provide-module sway %{\n\n# Ensure we're actually in Sway\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || \n    [ -n \"$SWAYSOCK\" ] ||\n    echo 'fail SWAYSOCK is not set'\n}\n\nrequire-module 'wayland'\n\nalias global sway-terminal-window wayland-terminal-window\n\ndefine-command sway-terminal-vertical -params 1.. -docstring '\n    sway-terminal-vertical <program> [<arguments>]: create a new terminal as a Sway window\n    The current pane is split into two, top and bottom\n    The program passed as argument will be executed in the new terminal' \\\n%{\n    nop %sh{swaymsg split vertical}\n    wayland-terminal-window %arg{@}\n}\ncomplete-command sway-terminal-vertical shell\n\ndefine-command sway-terminal-horizontal -params 1.. -docstring '\n    sway-terminal-horizontal <program> [<arguments>]: create a new terminal as a Sway window\n    The current pane is split into two, left and right\n    The program passed as argument will be executed in the new terminal' \\\n%{\n    nop %sh{swaymsg split horizontal}\n    wayland-terminal-window %arg{@}\n}\ncomplete-command sway-terminal-horizontal shell\n\ndefine-command sway-terminal-tab -params 1.. -docstring '\n    sway-terminal-tab <program> [<arguments>]: create a new terminal as a Sway window\n    The program passed as argument will be executed in the new terminal' \\\n%{\n    nop %sh{swaymsg 'split horizontal; layout tabbed'}\n    wayland-terminal-window %arg{@}\n}\ncomplete-command sway-terminal-tab shell\n\ndefine-command sway-focus-pid -hidden %{\n    evaluate-commands %sh{\n        pid=$kak_client_pid\n\n        # Try to focus a window with the current PID, walking up the tree of \n        # parent processes until the focus eventually succeeds\n        while ! swaymsg [pid=$pid] focus > /dev/null 2> /dev/null ; do\n            # Replace the current PID with its parent PID\n            pid=$(ps -p $pid -o ppid=)\n\n            # If we couldn't get a PPID for some reason, or it's 1 or less, we \n            # should just fail. \n            if [ -z $pid ] || [ $pid -le 1 ]; then\n                echo \"fail Can't find PID for Sway window to focus\"\n                break\n            fi\n        done\n    }\n}\n\ndefine-command sway-focus -params ..1 -docstring '\nsway-focus [<kakoune_client>]: focus a given client''s window.\nIf no client is passed, then the current client is used' \\\n%{\n    # Quick branch to make sure we're calling sway-focus-pid from the client \n    # the user wants to focus on.\n    evaluate-commands %sh{\n        if [ $# -eq 1 ]; then\n            printf \"evaluate-commands -client '%s' sway-focus-pid\" \"$1\"\n        else\n            echo sway-focus-pid\n        fi\n    }\n}\ncomplete-command -menu sway-focus client\n\nalias global focus sway-focus\n\n}\n"
  },
  {
    "path": "rc/windowing/tmux.kak",
    "content": "# http://tmux.github.io/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nprovide-module tmux %{\n\n# ensure we're running under tmux\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || [ -n \"$TMUX\" ] || echo 'fail tmux not detected'\n}\n\ndefine-command -hidden -params 2.. tmux-terminal-impl %{\n    evaluate-commands %sh{\n        tmux=${kak_client_env_TMUX:-$TMUX}\n        if [ -z \"$tmux\" ]; then\n            echo \"fail 'This command is only available in a tmux session'\"\n            exit\n        fi\n        tmux_args=\"$1\"\n        if [ \"${1%%-*}\" = split ]; then\n            tmux_args=\"$tmux_args -t ${kak_client_env_TMUX_PANE}\"\n        elif [ \"${1%% *}\" = new-window ]; then\n            session_id=$(tmux display-message -p -t ${kak_client_env_TMUX_PANE} '#{session_id}')\n            tmux_args=\"$tmux_args -t $session_id\"\n        fi\n        shift\n        # ideally we should escape single ';' to stop tmux from interpreting it as a new command\n        # but that's probably too rare to care\n        if [ -n \"$TMPDIR\" ]; then\n            TMUX=$tmux tmux $tmux_args env TMPDIR=\"$TMPDIR\" \"$@\"\n        else\n            TMUX=$tmux tmux $tmux_args \"$@\"\n        fi\n    }\n}\n\ndefine-command tmux-terminal-vertical -params 1.. -docstring '\ntmux-terminal-vertical <program> [<arguments>]: create a new terminal as a tmux pane\nThe current pane is split into two, top and bottom\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    tmux-terminal-impl 'split-window -v' %arg{@}\n}\ncomplete-command tmux-terminal-vertical shell\n\ndefine-command tmux-terminal-horizontal -params 1.. -docstring '\ntmux-terminal-horizontal <program> [<arguments>]: create a new terminal as a tmux pane\nThe current pane is split into two, left and right\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    tmux-terminal-impl 'split-window -h' %arg{@}\n}\ncomplete-command tmux-terminal-horizontal shell\n\ndefine-command tmux-terminal-window -params 1.. -docstring '\ntmux-terminal-window <program> [<arguments>]: create a new terminal as a tmux window\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    tmux-terminal-impl 'new-window' %arg{@}\n}\ncomplete-command tmux-terminal-window shell\n\ndefine-command tmux-focus -params ..1 -docstring '\ntmux-focus [<client>]: focus the given client\nIf no client is passed then the current one is used' \\\n%{\n    evaluate-commands %sh{\n        if [ $# -eq 1 ]; then\n            printf \"evaluate-commands -client '%s' focus\" \"$1\"\n        elif [ -n \"${kak_client_env_TMUX}\" ]; then\n            # select-pane makes the pane active in the window, but does not select the window. Both select-pane\n            # and select-window should be invoked in order to select a pane on a currently not focused window.\n            TMUX=\"${kak_client_env_TMUX}\" tmux select-window -t \"${kak_client_env_TMUX_PANE}\" \\; \\\n                                               select-pane   -t \"${kak_client_env_TMUX_PANE}\" > /dev/null\n        fi\n    }\n}\ncomplete-command -menu tmux-focus client\n\n## The default behaviour for the `new` command is to open an horizontal pane in a tmux session\nalias global focus tmux-focus\n\n}\n"
  },
  {
    "path": "rc/windowing/wayland.kak",
    "content": "# wayland\n\nprovide-module wayland %{\n\n# ensure that we're running in the right environment\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || [ -n \"$WAYLAND_DISPLAY\" ] || echo 'fail WAYLAND_DISPLAY is not set'\n}\n\n# termcmd should be set such as the next argument is the whole\n# command line to execute\ndeclare-option -docstring %{shell command run to spawn a new terminal\nA shell command is appended to the one set in this option at runtime} \\\n    str termcmd %sh{\n    for termcmd in 'alacritty      -e sh -c' \\\n                   'kitty             sh -c' \\\n                   'foot              sh -c' \\\n                   'termite        -e      ' \\\n                   'wterm          -e sh -c' \\\n                   'gnome-terminal -e      ' \\\n                   'kgx            -e      ' \\\n                   'xfce4-terminal -e      ' \\\n                   'konsole        -e      '; do\n        terminal=${termcmd%% *}\n        if command -v $terminal >/dev/null 2>&1; then\n            printf %s\\\\n \"$termcmd\"\n            exit\n        fi\n    done\n}\n\ndefine-command wayland-terminal-window -params 1.. -docstring '\nwayland-terminal-window <program> [<arguments>]: create a new terminal as a Wayland window\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    evaluate-commands -save-regs 'a' %{\n        set-register a %arg{@}\n        evaluate-commands %sh{\n            if [ -z \"${kak_opt_termcmd}\" ]; then\n                echo \"fail 'termcmd option is not set'\"\n                exit\n            fi\n            termcmd=$kak_opt_termcmd\n            args=$kak_quoted_reg_a\n            unset kak_opt_termcmd kak_quoted_reg_a\n            setsid ${termcmd} \"$args\" < /dev/null > /dev/null 2>&1 &\n        }\n    }\n}\ncomplete-command wayland-terminal-window shell\n\ndefine-command wayland-focus -params ..1 -docstring '\nwayland-focus [<kakoune_client>]: focus a given client''s window\nIf no client is passed, then the current client is used' \\\n%{\n    fail 'Focusing specific windows in most Wayland window managers is unsupported'\n}\ncomplete-command -menu wayland-focus client\n\nalias global focus wayland-focus\n\n# deprecated\ndefine-command -hidden wayland-terminal -params 1.. %{\n    wayland-terminal-window %arg{@}\n}\n\n}\n"
  },
  {
    "path": "rc/windowing/wezterm.kak",
    "content": "# https://wezfurlong.org/wezterm/index.html\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n\nprovide-module wezterm %{\n\n# ensure that we're running under screen\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || [ -n \"$WEZTERM_UNIX_SOCKET\" ] || echo 'fail wezterm not detected'\n}\n\ndefine-command wezterm-terminal-impl -hidden -params 2.. %{\n    nop %sh{\n        wezterm cli \"$@\"\n    }\n}\n\ndefine-command wezterm-terminal-vertical -params 1.. -docstring '\nwezterm-terminal-vertical <program> [<arguments>]: create a new terminal as a wezterm pane\nThe current pane is split into two, top and bottom\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    wezterm-terminal-impl split-pane --cwd \"%val{client_env_PWD}\" --bottom --pane-id \"%val{client_env_WEZTERM_PANE}\" -- %arg{@}\n}\ncomplete-command wezterm-terminal-vertical shell\n\ndefine-command wezterm-terminal-horizontal -params 1.. -docstring '\nwezterm-terminal-horizontal <program> [<arguments>]: create a new terminal as a wezterm pane\nThe current pane is split into two, left and right\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    wezterm-terminal-impl split-pane --cwd \"%val{client_env_PWD}\" --right --pane-id \"%val{client_env_WEZTERM_PANE}\" -- %arg{@}\n}\ncomplete-command wezterm-terminal-horizontal shell\n\ndefine-command wezterm-terminal-tab -params 1.. -docstring '\nwezterm-terminal-tab <program> [<arguments>]: create a new terminal as a wezterm tab\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    wezterm-terminal-impl spawn --cwd \"%val{client_env_PWD}\" --pane-id \"%val{client_env_WEZTERM_PANE}\" -- %arg{@}\n}\ncomplete-command wezterm-terminal-tab shell\n\ndefine-command wezterm-terminal-window -params 1.. -docstring '\nwezterm-terminal-window <program> [<arguments>]: create a new terminal as a wezterm window\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    wezterm-terminal-impl spawn --cwd \"%val{client_env_PWD}\" --new-window --pane-id \"%val{client_env_WEZTERM_PANE}\" -- %arg{@}\n}\ncomplete-command wezterm-terminal-window shell\n\ndefine-command wezterm-focus -params ..1 -docstring '\nwezterm-focus [<client>]: focus the given client\nIf no client is passed then the current one is used' \\\n%{\n    evaluate-commands %sh{\n        if [ $# -eq 1 ]; then\n            printf %s\\\\n \"\n                evaluate-commands -client '$1' focus\n                \"\n        elif [ -n \"${kak_client_env_WEZTERM_PANE}\" ]; then\n            wezterm cli activate-pane --pane-id \"${kak_client_env_WEZTERM_PANE}\" > /dev/null 2>&1\n        fi\n    }\n}\ncomplete-command -menu wezterm-focus client\n\nalias global focus wezterm-focus\n\n}\n"
  },
  {
    "path": "rc/windowing/x11.kak",
    "content": "# x11\n\nprovide-module x11 %{\n\n# ensure that we're running in the right environment\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || [ -n \"$DISPLAY\" ] || echo 'fail DISPLAY is not set'\n}\n\n# termcmd should be set such as the next argument is the whole\n# command line to execute\ndeclare-option -docstring %{shell command run to spawn a new terminal\nA shell command is appended to the one set in this option at runtime} \\\n    str termcmd %sh{\n    for termcmd in 'alacritty      -e sh -c' \\\n                   'kitty             sh -c' \\\n                   'termite        -e      ' \\\n                   'urxvt          -e sh -c' \\\n                   'rxvt           -e sh -c' \\\n                   'st             -e sh -c' \\\n                   'xterm          -e sh -c' \\\n                   'roxterm        -e sh -c' \\\n                   'mintty         -e sh -c' \\\n                   'sakura         -x      ' \\\n                   'gnome-terminal -e      ' \\\n                   'xfce4-terminal -x sh -c' \\\n                   'konsole        -e      '; do\n        terminal=${termcmd%% *}\n        if command -v $terminal >/dev/null 2>&1; then\n            printf %s\\\\n \"$termcmd\"\n            exit\n        fi\n    done\n}\n\ndefine-command x11-terminal-window -params 1.. -docstring '\nx11-terminal-window <program> [<arguments>]: create a new terminal as an X11 window\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    evaluate-commands -save-regs 'a' %{\n        set-register a %arg{@}\n        evaluate-commands %sh{\n            if [ -z \"${kak_opt_termcmd}\" ]; then\n                echo \"fail 'termcmd option is not set'\"\n                exit\n            fi\n            termcmd=$kak_opt_termcmd\n            args=$kak_quoted_reg_a\n            unset kak_opt_termcmd kak_quoted_reg_a\n            setsid ${termcmd} \"$args\" < /dev/null > /dev/null 2>&1 &\n        }\n    }\n}\ncomplete-command x11-terminal-window shell \n\ndefine-command x11-focus -params ..1 -docstring '\nx11-focus [<kakoune_client>]: focus a given client''s window\nIf no client is passed, then the current client is used' \\\n%{\n    evaluate-commands %sh{\n        if [ $# -eq 1 ]; then\n            printf \"evaluate-commands -client '%s' focus\" \"$1\"\n        else\n            xdotool windowactivate $kak_client_env_WINDOWID > /dev/null ||\n            echo 'fail failed to run x11-focus, see *debug* buffer for details'\n        fi\n    }\n}\ncomplete-command -menu x11-focus client \n\nalias global focus x11-focus\n\n# deprecated\ndefine-command -hidden x11-terminal -params 1.. %{\n    x11-terminal-window %arg{@}\n}\n\n}\n"
  },
  {
    "path": "rc/windowing/zellij.kak",
    "content": "# https://zellij.dev/\n# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\nprovide-module zellij %{\n\n# ensure we're running under zellij\nevaluate-commands %sh{\n    [ -z \"${kak_opt_windowing_modules}\" ] || [ -n \"$ZELLIJ\" -a -n \"$ZELLIJ_SESSION_NAME\" ] || echo 'fail zellij not detected'\n}\n\ndefine-command -params 1.. zellij-action %{ nop %sh{\n    zellij --session \"$kak_client_env_ZELLIJ_SESSION_NAME\" action \"$@\"\n}}\ndefine-command -hidden -params 2.. zellij-run %{ nop %sh{\n    zellij_run_options=$1\n    shift\n    zellij --session \"$kak_client_env_ZELLIJ_SESSION_NAME\" run $zellij_run_options -- \"$@\"\n}}\n\ndefine-command -hidden -params 1.. zellij-terminal-window %{\n    zellij-run \"--close-on-exit\" %arg{@}\n}\ncomplete-command zellij-terminal-window shell\n\ndefine-command zellij-terminal-vertical -params 1.. -docstring '\nzellij-terminal-vertical <program> [<arguments>]: create a new terminal as a zellij pane\nThe current pane is split into two, top and bottom\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    zellij-run \"--direction down\" %arg{@}\n}\ncomplete-command zellij-terminal-vertical shell\n\ndefine-command zellij-terminal-horizontal -params 1.. -docstring '\nzellij-terminal-horizontal <program> [<arguments>]: create a new terminal as a zellij pane\nThe current pane is split into two, left and right\nThe program passed as argument will be executed in the new terminal' \\\n%{\n    zellij-run \"--direction right\" %arg{@}\n}\ncomplete-command zellij-terminal-horizontal shell\n\ndefine-command zellij-focus -params ..1 -docstring '\nzellij-focus [<client>]: focus the given client\nIf no client is passed then the current one is used' \\\n%{ evaluate-commands %sh{\n    if [ $# -eq 1 ]; then\n        printf \"evaluate-commands -client '%s' focus\" \"$1\"\n    elif [ -n \"${kak_client_env_ZELLIJ}\" ]; then\n        output=$(mktemp -d \"${TMPDIR:-/tmp}\"/kak-zellij.XXXXXXXX)/dump-screen\n        pane_count=0\n        while [ $((pane_count+=1)) -lt 10 ]; do\n             if zellij action dump-screen \"$output\" && grep \"${kak_client}@\\[$kak_session\\]\" \"$output\" > /dev/null ; then\n                 break;\n             fi\n             zellij action focus-next-pane\n        done\n        rm -r $(dirname $output)\n    fi\n}}\ncomplete-command -menu zellij-focus client\n\n## The default behaviour for the `new` command is to open an horizontal pane in a zellij session\nalias global focus zellij-focus\n\n}\n\n"
  },
  {
    "path": "share/kak/kakrc",
    "content": "def -params 1 -docstring \"colorscheme <name>: enable named colorscheme\" \\\n    -shell-script-candidates %{\n    find -L \"${kak_runtime}/colors\" \"${kak_config}/colors\" -type f -name '*\\.kak' \\\n        | while read -r filename; do\n            basename=\"${filename##*/}\"\n            printf %s\\\\n \"${basename%.*}\"\n        done | sort -u\n  } \\\n  colorscheme %{ evaluate-commands %sh{\n    find_colorscheme() {\n        find -L \"${1}\" -type f -name \"${2}\".kak | head -n 1\n    }\n\n    filename=\"\"\n    if [ -d \"${kak_config}/colors\" ]; then\n        filename=$(find_colorscheme \"${kak_config}/colors\" \"${1}\")\n    fi\n    if [ -z \"${filename}\" ]; then\n        filename=$(find_colorscheme \"${kak_runtime}/colors\" \"${1}\")\n    fi\n\n    if [ -n \"${filename}\" ]; then\n        printf 'source %%{%s}' \"${filename}\"\n    else\n        echo \"fail 'No such colorscheme ${1}.kak'\"\n    fi\n}}\n\nevaluate-commands %sh{\n    autoload_directory() {\n        find -L \"$1\" -type f -name '*\\.kak' \\\n            | sed 's/.*/try %{ source \"&\" } catch %{ echo -debug Autoload: could not load \"&\" }/'\n    }\n\n    echo \"colorscheme default\"\n\n    if [ -d \"${kak_config}/autoload\" ]; then\n        autoload_directory ${kak_config}/autoload\n    elif [ -d \"${kak_runtime}/autoload\" ]; then\n        autoload_directory ${kak_runtime}/autoload\n    fi\n\n    if [ -f \"${kak_runtime}/kakrc.local\" ]; then\n        echo \"source '${kak_runtime}/kakrc.local'\"\n    fi\n\n    if [ -f \"${kak_config}/kakrc\" ]; then\n        echo \"source '${kak_config}/kakrc'\"\n    fi\n}\n"
  },
  {
    "path": "src/.gdbinit",
    "content": "set print pretty\nbreak Kakoune::on_assert_failed\n"
  },
  {
    "path": "src/alias_registry.cc",
    "content": "#include \"alias_registry.hh\"\n\n#include \"command_manager.hh\"\n\nnamespace Kakoune\n{\n\nvoid AliasRegistry::add_alias(String alias, String command)\n{\n    kak_assert(not alias.empty());\n    kak_assert(CommandManager::instance().command_defined(command));\n    auto it = m_aliases.find(alias);\n    if (it == m_aliases.end())\n        m_aliases.insert({std::move(alias), std::move(command) });\n    else\n        it->value = std::move(command);\n}\n\nvoid AliasRegistry::remove_alias(StringView alias)\n{\n    m_aliases.remove(alias);\n}\n\nStringView AliasRegistry::operator[](StringView alias) const\n{\n    auto it = m_aliases.find(alias);\n    if (it != m_aliases.end())\n        return it->value;\n    else if (m_parent)\n        return (*m_parent)[alias];\n    else\n        return StringView{};\n}\n\nVector<StringView> AliasRegistry::aliases_for(StringView command) const\n{\n    Vector<StringView> res;\n    if (m_parent)\n        res = m_parent->aliases_for(command);\n\n    for (auto& alias : m_aliases)\n    {\n        if (alias.value == command)\n            res.emplace_back(alias.key);\n    }\n\n    return res;\n}\n\n}\n"
  },
  {
    "path": "src/alias_registry.hh",
    "content": "#ifndef alias_registry_hh_INCLUDED\n#define alias_registry_hh_INCLUDED\n\n#include \"safe_ptr.hh\"\n#include \"string.hh\"\n#include \"ranges.hh\"\n#include \"hash_map.hh\"\n\nnamespace Kakoune\n{\n\nclass AliasRegistry : public SafeCountable\n{\npublic:\n    AliasRegistry(AliasRegistry& parent) : SafeCountable{}, m_parent(&parent) {}\n\n    void reparent(AliasRegistry& parent) { m_parent = &parent; }\n\n    void add_alias(String alias, String command);\n    void remove_alias(StringView alias);\n    StringView operator[](StringView alias) const;\n\n    Vector<StringView> aliases_for(StringView command) const;\n\n    auto flatten_aliases() const\n    {\n        auto merge = [](auto&& first, const AliasMap& second) {\n            return concatenated(std::forward<decltype(first)>(first)\n                                | filter([&second](auto& i) { return not second.contains(i.key); }),\n                                second);\n        };\n        static const AliasMap empty;\n        auto& parent = m_parent ? m_parent->m_aliases : empty;\n        auto& grand_parent = (m_parent and m_parent->m_parent) ? m_parent->m_parent->m_aliases : empty;\n        return merge(merge(grand_parent, parent), m_aliases);\n    }\n\nprivate:\n    friend class Scope;\n    AliasRegistry() = default;\n\n    SafePtr<AliasRegistry> m_parent;\n    using AliasMap = HashMap<String, String, MemoryDomain::Aliases>;\n    AliasMap m_aliases;\n};\n\n}\n\n#endif // alias_registry_hh_INCLUDED\n"
  },
  {
    "path": "src/array.hh",
    "content": "#ifndef array_hh_INCLUDED\n#define array_hh_INCLUDED\n\n#include <utility>\n#include <stddef.h>\n\n#include \"array_view.hh\"\n\nnamespace Kakoune\n{\n\ntemplate<typename T, size_t N>\nstruct Array\n{\n    constexpr size_t size() const { return N; }\n    constexpr const T& operator[](int i) const { return m_data[i]; }\n    constexpr const T* begin() const { return m_data; }\n    constexpr const T* end() const { return m_data+N; }\n\n    constexpr T& operator[](int i) { return m_data[i]; }\n    constexpr T* begin() { return m_data; }\n    constexpr T* end() { return m_data+N; }\n\n    constexpr operator ArrayView<T>() { return {m_data, N}; }\n    constexpr operator ConstArrayView<T>() const { return {m_data, N}; }\n\n    T m_data[N];\n};\n\ntemplate<typename T, typename... U> requires (std::is_same_v<T, U> and ...)\nArray(T, U...) -> Array<T, 1 + sizeof...(U)>;\n\ntemplate<typename T, size_t N, size_t... Indices>\nconstexpr Array<T, N> make_array(const T (&data)[N], std::index_sequence<Indices...>)\n{\n    static_assert(sizeof...(Indices) == N, \"size mismatch\");\n    return {{data[Indices]...}};\n}\n\ntemplate<typename T, size_t N>\nconstexpr Array<T, N> make_array(const T (&data)[N])\n{\n    return make_array(data, std::make_index_sequence<N>());\n}\n\n}\n\n#endif // array_hh_INCLUDED\n"
  },
  {
    "path": "src/array_view.hh",
    "content": "#ifndef array_view_hh_INCLUDED\n#define array_view_hh_INCLUDED\n\n#include <initializer_list>\n#include <iterator>\n\nnamespace Kakoune\n{\n\n// An ArrayView provides a typed, non owning view of a memory\n// range with an interface similar to std::vector.\ntemplate<typename T>\nclass ArrayView\n{\npublic:\n    using size_t = std::size_t;\n\n    constexpr ArrayView()\n        : m_pointer(nullptr), m_size(0) {}\n\n    constexpr ArrayView(T& oneval)\n        : m_pointer(&oneval), m_size(1) {}\n\n    constexpr ArrayView(T* pointer, size_t size)\n        : m_pointer(pointer), m_size(size) {}\n\n    constexpr ArrayView(T* begin, T* end)\n        : m_pointer(begin), m_size(end - begin) {}\n\n    template<typename It>\n        requires std::contiguous_iterator<It> and std::is_same_v<std::iter_value_t<It>, T>\n    constexpr ArrayView(It begin, It end)\n        : m_pointer(&*begin), m_size(end - begin) {}\n\n    template<size_t N>\n    constexpr ArrayView(T(&array)[N]) : m_pointer(array), m_size(N) {}\n\n    template<typename Container>\n        requires (sizeof(decltype(*std::declval<Container>().data())) == sizeof(T))\n    constexpr ArrayView(Container&& c)\n        : m_pointer(c.data()), m_size(c.size()) {}\n\n    constexpr ArrayView(const std::initializer_list<T>& v)\n        : m_pointer(v.begin()), m_size(v.size()) {}\n\n    constexpr T* pointer() const { return m_pointer; }\n    constexpr size_t size() const { return m_size; }\n\n    [[gnu::always_inline]]\n    constexpr T& operator[](size_t n) const { return *(m_pointer + n); }\n\n    constexpr T* begin() const { return m_pointer; }\n    constexpr T* end()   const { return m_pointer+m_size; }\n\n    using reverse_iterator = std::reverse_iterator<T*>;\n    constexpr reverse_iterator rbegin() const { return reverse_iterator(m_pointer+m_size); }\n    constexpr reverse_iterator rend()   const { return reverse_iterator(m_pointer); }\n\n    constexpr T& front() const { return *m_pointer; }\n    constexpr T& back()  const { return *(m_pointer + m_size - 1); }\n\n    constexpr bool empty() const { return m_size == 0; }\n\n    constexpr ArrayView subrange(size_t first, size_t count = -1) const\n    {\n        auto min = [](size_t a, size_t b) { return a < b ? a : b; };\n        return ArrayView(m_pointer + min(first, m_size),\n                         min(count, m_size - min(first, m_size)));\n    }\n\nprivate:\n    T* m_pointer;\n    size_t m_size;\n};\n\ntemplate<typename It>\n    requires std::contiguous_iterator<It>\nArrayView(It begin, It end) -> ArrayView<std::iter_value_t<It>>;\n\ntemplate<typename Container>\nArrayView(Container& c) -> ArrayView<std::remove_reference_t<decltype(*c.data())>>;\n\ntemplate<typename T>\nusing ConstArrayView = ArrayView<const T>;\n\ntemplate<typename T>\nbool operator==(ArrayView<T> lhs, ArrayView<T> rhs)\n{\n    if (lhs.size() != rhs.size())\n        return false;\n    for (int i = 0; i < lhs.size(); ++i)\n    {\n        if (lhs[i] != rhs[i])\n            return false;\n    }\n    return true;\n}\n\n}\n\n#endif // array_view_hh_INCLUDED\n"
  },
  {
    "path": "src/assert.cc",
    "content": "#include \"assert.hh\"\n\n#include \"backtrace.hh\"\n#include \"format.hh\"\n#include \"file.hh\"\n#include \"exception.hh\"\n#include \"debug.hh\"\n\n#include <sys/types.h>\n#include <unistd.h>\n\nnamespace Kakoune\n{\n\nstruct assert_failed : logic_error\n{\n    assert_failed(String message)\n        : m_message(std::move(message)) {}\n\n    StringView what() const override { return m_message; }\nprivate:\n    String m_message;\n};\n\nbool notify_fatal_error(StringView msg)\n{\n    if (not isatty(STDOUT_FILENO) or not isatty(STDIN_FILENO))\n        return false;\n\n    write(STDOUT_FILENO, format(\"\\033[;31;5;1mKakoune fatal error, q: exit, i: ignore or debug pid {}\\033[0m\", getpid()));\n    while (true)\n    {\n        if (unsigned char c = 0; read(STDIN_FILENO, &c, 1) < 0 or c == 'q')\n            return false;\n        else if (c == 'i')\n            return true;\n    }\n}\n\nvoid on_assert_failed(const char* message)\n{\n    String debug_info = format(\"pid: {}\\ncallstack:\\n{}\", getpid(), Backtrace{}.desc());\n    write_to_debug_buffer(format(\"assert failed: '{}'\\n{}\", message, debug_info));\n\n    const auto msg = format(\"{}\\n[Debug Infos]\\n{}\", message, debug_info);\n    if (not notify_fatal_error(msg))\n        throw assert_failed(msg);\n}\n\n}\n"
  },
  {
    "path": "src/assert.hh",
    "content": "#ifndef assert_hh_INCLUDED\n#define assert_hh_INCLUDED\n\nnamespace Kakoune\n{\n\nclass StringView;\n\n// return true if user asked to ignore the error\nbool notify_fatal_error(StringView message);\n\nvoid on_assert_failed(const char* message);\n\n}\n\n#define STRINGIFY(X) #X\n#define TOSTRING(X) STRINGIFY(X)\n\n#ifdef KAK_DEBUG\n    #define kak_assert(...) do { \\\n        if (not (__VA_ARGS__)) \\\n            on_assert_failed(\"assert failed \\\"\" #__VA_ARGS__ \\\n                             \"\\\" at \" __FILE__ \":\" TOSTRING(__LINE__)); \\\n    } while (false)\n\n    #define kak_expect_throw(exception_type, ...) try {\\\n        __VA_ARGS__; \\\n        on_assert_failed(\"expression \\\"\" #__VA_ARGS__ \\\n                         \"\\\" did not throw \\\"\" #exception_type \\\n                         \"\\\" at \" __FILE__ \":\" TOSTRING(__LINE__)); \\\n    } catch (exception_type &err) {}\n#else\n    #define kak_assert(...) do { (void)alignof(decltype(__VA_ARGS__)); } while(false)\n    #define kak_expect_throw(_, ...) do { (void)alignof(decltype(__VA_ARGS__)); } while(false)\n#endif\n\n\n#endif // assert_hh_INCLUDED\n"
  },
  {
    "path": "src/backtrace.cc",
    "content": "#include \"backtrace.hh\"\n\n#include \"string.hh\"\n#include \"format.hh\"\n\n#if defined(__GLIBC__) || defined(__APPLE__)\n# include <execinfo.h>\n#elif defined(__CYGWIN__)\n# include <windows.h>\n# include <dbghelp.h>\n# include <stdio.h>\n#endif\n\n#if defined(__linux__) || defined(__APPLE__)\n# include <cstdlib>\n#endif\n\nnamespace Kakoune\n{\n\nBacktrace::Backtrace()\n{\n    #if defined(__GLIBC__) || defined(__APPLE__)\n    num_frames = backtrace(stackframes, max_frames);\n    #elif defined(__CYGWIN__)\n    num_frames = CaptureStackBackTrace(0, max_frames, stackframes, nullptr);\n    #endif\n}\n\nString Backtrace::desc() const\n{\n    #if defined(__GLIBC__) || defined(__APPLE__)\n    char** symbols = backtrace_symbols(stackframes, num_frames);\n    ByteCount size = 0;\n    for (int i = 0; i < num_frames; ++i)\n        size += strlen(symbols[i]) + 1;\n\n    String res; res.reserve(size);\n    for (int i = 0; i < num_frames; ++i)\n    {\n        res += symbols[i];\n        res += \"\\n\";\n    }\n    free(symbols);\n    return res;\n    #elif defined(__CYGWIN__)\n    HANDLE process = GetCurrentProcess();\n\n    static bool symbols_initialized = false;\n    if (not symbols_initialized)\n    {\n        SymInitialize(process, nullptr, true);\n        symbols_initialized = true;\n    }\n\n    alignas(SYMBOL_INFO) char symbol_info_buffer[sizeof(SYMBOL_INFO) + 256];\n    SYMBOL_INFO* symbol_info = reinterpret_cast<SYMBOL_INFO*>(symbol_info_buffer);\n    symbol_info->MaxNameLen = 255;\n    symbol_info->SizeOfStruct = sizeof(SYMBOL_INFO);\n\n    String res; // res.reserve(num_frames * 276);\n    for (int i = 0; i < num_frames; ++i)\n    {\n        SymFromAddr(process, (DWORD64)stackframes[i], 0, symbol_info);\n        char desc[276];\n        format_to(desc, \"0x{} ({})\\n\", hex(symbol_info->Address), symbol_info->Name);\n        res += desc;\n    }\n    return res;\n    #else\n    return \"<not implemented>\";\n    #endif\n}\n\n}\n"
  },
  {
    "path": "src/backtrace.hh",
    "content": "#ifndef backtrace_hh_INCLUDED\n#define backtrace_hh_INCLUDED\n\nnamespace Kakoune\n{\n\nclass String;\n\nstruct Backtrace\n{\n    static constexpr int max_frames = 16;\n    void* stackframes[max_frames];\n    int num_frames = 0;\n\n    Backtrace();\n    String desc() const;\n};\n\n}\n\n#endif // backtrace_hh_INCLUDED\n\n"
  },
  {
    "path": "src/buffer.cc",
    "content": "#include \"buffer.hh\"\n\n#include \"assert.hh\"\n#include \"buffer_manager.hh\"\n#include \"buffer_utils.hh\"\n#include \"hook_manager.hh\"\n#include \"option_manager.hh\"\n#include \"client.hh\"\n#include \"context.hh\"\n#include \"diff.hh\"\n#include \"file.hh\"\n#include \"flags.hh\"\n#include \"option_types.hh\"\n#include \"ranges.hh\"\n#include \"shared_string.hh\"\n#include \"unit_tests.hh\"\n#include \"utils.hh\"\n#include \"window.hh\"\n\n#include <algorithm>\n\nnamespace Kakoune\n{\n\nBuffer::HistoryNode::HistoryNode(HistoryId parent)\n    : parent{parent}, committed{Clock::now()}\n{}\n\nBuffer::Buffer(String name, Flags flags, BufferLines lines,\n               ByteOrderMark bom, EolFormat eolformat, FinalEol finaleol,\n               FsStatus fs_status)\n    : Scope{GlobalScope::instance()},\n      m_filename{(flags & Flags::File) ? real_path(parse_filename(name)) : String{}},\n      m_display_name{(flags & Flags::File) ? compact_path(m_filename) : std::move(name)},\n      m_flags{flags | Flags::NoUndo},\n      m_history{{HistoryId::Invalid}},\n      m_history_id{HistoryId::First},\n      m_last_save_history_id{HistoryId::First},\n      m_fs_status{fs_status}\n{\n    #ifdef KAK_DEBUG\n    for (auto& line : lines)\n        kak_assert(not (line->length == 0) and\n                   line->data()[line->length-1] == '\\n');\n    #endif\n    static_cast<BufferLines&>(m_lines) = std::move(lines);\n\n    m_changes.push_back({ Change::Insert, {0,0}, line_count() });\n\n    options().get_local_option(\"eolformat\").set(eolformat);\n    options().get_local_option(\"finaleol\").set(finaleol);\n    options().get_local_option(\"BOM\").set(bom);\n    options().get_local_option(\"readonly\").set((bool)(flags & Flags::ReadOnly));\n\n    // now we may begin to record undo data\n    if (not (flags & Flags::NoUndo))\n        m_flags &= ~Flags::NoUndo;\n}\n\nvoid Buffer::on_registered()\n{\n    // Ignore debug buffer, as it can be created in many\n    // corner cases (including while destroying the BufferManager\n    // if a BufClose hooks triggers writing to it).\n    if (m_flags & Flags::Debug)\n        return;\n\n    options().register_watcher(*this);\n\n    if (m_flags & Buffer::Flags::NoHooks)\n    {\n        on_option_changed(options()[\"readonly\"]);\n        return;\n    }\n\n    m_flags |= Flags::NoBufSetOption;\n\n    run_hook_in_own_context(Hook::BufCreate, (m_flags & Flags::File) ? m_filename : m_display_name);\n\n    if (m_flags & Flags::File)\n    {\n        if (m_flags & Buffer::Flags::New)\n            run_hook_in_own_context(Hook::BufNewFile, (m_flags & Flags::File) ? m_filename : m_display_name);\n        else\n        {\n            kak_assert(m_fs_status.timestamp != InvalidTime);\n            run_hook_in_own_context(Hook::BufOpenFile, (m_flags & Flags::File) ? m_filename : m_display_name);\n        }\n    }\n    m_flags &= ~Flags::NoBufSetOption;\n\n    for (auto& option : options().flatten_options()\n                      | transform(&UniquePtr<Option>::get)\n                      | gather<Vector<Option*>>())\n        on_option_changed(*option);\n}\n\nvoid Buffer::on_unregistered()\n{\n    if (m_flags & Flags::Debug)\n        return;\n\n    options().unregister_watcher(*this);\n    run_hook_in_own_context(Hook::BufClose, (m_flags & Flags::File) ? m_filename : m_display_name);\n}\n\nBuffer::~Buffer()\n{\n    m_values.clear();\n}\n\nbool Buffer::set_name(String name)\n{\n    Buffer* other = BufferManager::instance().get_buffer_ifp(name);\n    if (other == nullptr or other == this)\n    {\n        if (m_flags & Flags::File)\n        {\n            m_filename = real_path(name);\n            m_display_name = compact_path(m_filename);\n            if (m_flags & Buffer::Flags::File and not file_exists(m_filename))\n            {\n                m_flags |= Buffer::Flags::New;\n                m_last_save_history_id = HistoryId::Invalid;\n            }\n        }\n        else\n        {\n            m_filename = String{};\n            m_display_name = std::move(name);\n        }\n        return true;\n    }\n    return false;\n}\n\nvoid Buffer::throw_if_read_only() const\n{\n    if (m_flags & Flags::ReadOnly)\n        throw runtime_error(\"buffer is read-only\");\n}\n\nvoid Buffer::update_display_name()\n{\n    if (m_flags & Flags::File)\n        m_display_name = compact_path(m_filename);\n}\n\nBufferIterator Buffer::iterator_at(BufferCoord coord) const\n{\n    kak_assert(is_valid(coord));\n    return {*this, coord};\n}\n\nBufferCoord Buffer::clamp(BufferCoord coord) const\n{\n    if (coord > back_coord())\n        coord = back_coord();\n    kak_assert(coord.line >= 0 and coord.line < line_count());\n    ByteCount max_col = std::max(0_byte, m_lines[coord.line].length() - 1);\n    coord.column = Kakoune::clamp(coord.column, 0_byte, max_col);\n    return coord;\n}\n\nBufferCoord Buffer::offset_coord(BufferCoord coord, CharCount offset, ColumnCount) const\n{\n    return utf8::advance(iterator_at(coord), offset < 0 ? begin() : end()-1, offset).coord();\n}\n\nBufferCoordAndTarget Buffer::offset_coord(BufferCoordAndTarget coord, LineCount offset, ColumnCount tabstop) const\n{\n    const auto column = coord.target == -1 ? get_column(*this, tabstop, coord) : coord.target;\n    const bool avoid_eol = coord.target < max_column;\n    const auto line = Kakoune::clamp(coord.line + offset, 0_line, line_count()-1);\n    const auto max_byte = m_lines[line].length()-1;\n    const auto max_column = get_column(*this, tabstop, {line, max_byte});\n    const auto final_column = std::max(0_col, std::min(column, max_column - (avoid_eol ? 1 : 0)));\n    return {line, std::min(max_byte, get_byte_to_column(*this, tabstop, {line, final_column})), column};\n}\n\nString Buffer::string(BufferCoord begin, BufferCoord end) const\n{\n    String res;\n    const auto last_line = std::min(end.line, line_count()-1);\n    for (auto line = begin.line; line <= last_line; ++line)\n    {\n        ByteCount start = 0;\n        if (line == begin.line)\n            start = begin.column;\n        ByteCount count = -1;\n        if (line == end.line)\n            count = end.column - start;\n        res += m_lines[line].substr(start, count);\n    }\n    return res;\n}\n\nBuffer::Modification Buffer::Modification::inverse() const\n{\n    return {type == Insert ? Erase : Insert, coord, content};\n}\n\nvoid Buffer::reload(BufferLines lines, ByteOrderMark bom, EolFormat eolformat, FinalEol finaleol, FsStatus fs_status)\n{\n    const bool record_undo = not (m_flags & Flags::NoUndo);\n\n    commit_undo_group();\n\n    if (not record_undo)\n    {\n        // Erase history about to be invalidated history\n        m_history_id = HistoryId::First;\n        m_last_save_history_id = HistoryId::First;\n        m_history = {HistoryNode{HistoryId::Invalid}};\n\n        m_changes.push_back({ Change::Erase, {0,0}, line_count() });\n        static_cast<BufferLines&>(m_lines) = std::move(lines);\n        m_changes.push_back({ Change::Insert, {0,0}, line_count() });\n    }\n    else\n    {\n        Vector<Diff> diff;\n        for_each_diff(m_lines.begin(), m_lines.size(),\n                      lines.begin(), lines.size(),\n                      [&diff](DiffOp op, int len)\n                      { diff.push_back({op, len}); },\n                      [](const StringDataPtr& lhs, const StringDataPtr& rhs)\n                      { return lhs->strview() == rhs->strview(); });\n\n        auto read_it = m_lines.begin();\n        auto write_it = m_lines.begin();\n        auto new_it = lines.begin();\n        for (auto [op, len] : diff)\n        {\n            kak_assert(read_it >= write_it);\n            if (op == DiffOp::Keep)\n            {\n                if (read_it != write_it)\n                    std::move(read_it, read_it + len, write_it);\n                write_it += len;\n                read_it += len;\n                new_it += len;\n            }\n            else if (op == DiffOp::Add)\n            {\n                const LineCount cur_line = (int)(write_it - m_lines.begin());\n                for (LineCount line = 0; line < len; ++line)\n                    m_current_undo_group.push_back({Modification::Insert, cur_line + line, *(new_it + (int)line)});\n                m_changes.push_back({Change::Insert, cur_line, cur_line + len});\n\n                if (read_it != write_it)\n                {\n                    auto count = std::min(len, static_cast<int>(read_it - write_it));\n                    write_it = std::copy(new_it, new_it + count, write_it);\n                    new_it += count;\n                    if (len == count)\n                        continue;\n                    len -= count;\n                }\n\n                auto read_pos = read_it - m_lines.begin();\n                write_it = m_lines.insert(write_it, new_it, new_it + len) + len;\n                read_it = m_lines.begin() + read_pos + len;\n                new_it += len;\n            }\n            else if (op == DiffOp::Remove)\n            {\n                const LineCount cur_line = (int)(write_it - m_lines.begin());\n                for (LineCount line = len-1; line >= 0; --line)\n                    m_current_undo_group.push_back({\n                        Modification::Erase, cur_line + line,\n                        *(read_it + (size_t)line)});\n\n                read_it += len;\n                m_changes.push_back({ Change::Erase, cur_line, cur_line + len });\n            }\n        }\n        m_lines.erase(write_it, m_lines.end());\n    }\n\n    commit_undo_group();\n\n    options().get_local_option(\"eolformat\").set(eolformat);\n    options().get_local_option(\"finaleol\").set(finaleol);\n    options().get_local_option(\"BOM\").set(bom);\n\n    m_last_save_history_id = m_history_id;\n    m_fs_status = fs_status;\n}\n\nvoid Buffer::commit_undo_group()\n{\n    if (m_flags & Flags::NoUndo)\n        return;\n\n    if (m_current_undo_group.empty())\n        return;\n\n    const HistoryId id = next_history_id();\n    m_history.push_back({m_history_id});\n    m_history.back().undo_group = std::move(m_current_undo_group);\n    m_current_undo_group.clear();\n    current_history_node().redo_child = id;\n    m_history_id = id;\n}\n\nbool Buffer::undo(size_t count)\n{\n    throw_if_read_only();\n\n    commit_undo_group();\n\n    if (current_history_node().parent == HistoryId::Invalid)\n        return false;\n\n    while (count-- != 0 and current_history_node().parent != HistoryId::Invalid)\n    {\n        for (const Modification& modification : current_history_node().undo_group | reverse())\n            apply_modification(modification.inverse());\n\n        m_history_id = current_history_node().parent;\n    }\n\n    return true;\n}\n\nbool Buffer::redo(size_t count)\n{\n    throw_if_read_only();\n\n    if (current_history_node().redo_child == HistoryId::Invalid or\n        not m_current_undo_group.empty())\n        return false;\n\n    while (count-- != 0 and current_history_node().redo_child != HistoryId::Invalid)\n    {\n        m_history_id = current_history_node().redo_child;\n\n        for (const Modification& modification : current_history_node().undo_group)\n            apply_modification(modification);\n    }\n    return true;\n}\n\nbool Buffer::move_to(HistoryId id)\n{\n    if (id >= next_history_id())\n        return false;\n\n    throw_if_read_only();\n\n    commit_undo_group();\n\n    auto find_lowest_common_parent = [this](HistoryId a, HistoryId b) {\n        auto depth_of = [this](HistoryId id) {\n            size_t depth = 0;\n            for (; history_node(id).parent != HistoryId::Invalid; id = history_node(id).parent)\n                ++depth;\n            return depth;\n        };\n        auto depthA = depth_of(a), depthB = depth_of(b);\n\n        for (; depthA > depthB; --depthA)\n            a = history_node(a).parent;\n        for (; depthB > depthA; --depthB)\n            b = history_node(b).parent;\n\n        while (a != b)\n        {\n            a = history_node(a).parent;\n            b = history_node(b).parent;\n        }\n\n        kak_assert(a == b and a != HistoryId::Invalid);\n        return a;\n    };\n\n    auto parent = find_lowest_common_parent(m_history_id, id);\n\n    // undo up to common parent\n    for (auto id = m_history_id; id != parent; id = history_node(id).parent)\n    {\n        for (const Modification& modification : history_node(id).undo_group | reverse())\n            apply_modification(modification.inverse());\n    }\n\n    static void (*apply_from_parent)(Buffer&, HistoryId, HistoryId) =\n    [](Buffer& buffer, HistoryId parent, HistoryId id) {\n        if (id == parent)\n            return;\n\n        auto& node = buffer.history_node(id);\n        apply_from_parent(buffer, parent, node.parent);\n\n        buffer.history_node(node.parent).redo_child = id;\n\n        for (const Modification& modification : node.undo_group)\n            buffer.apply_modification(modification);\n    };\n\n    apply_from_parent(*this, parent, id);\n    m_history_id = id;\n    return true;\n}\n\nvoid Buffer::check_invariant() const\n{\n#ifdef KAK_DEBUG\n    kak_assert(not m_lines.empty());\n    for (auto& line : m_lines)\n    {\n        kak_assert(line->strview().length() > 0);\n        kak_assert(line->strview().back() == '\\n');\n    }\n#endif\n}\n\nBufferRange Buffer::do_insert(BufferCoord pos, StringView content)\n{\n    kak_assert(is_valid(pos));\n\n    if (content.empty())\n        return {pos, pos};\n\n    const bool at_end = is_end(pos);\n    const bool append_lines = at_end and (m_lines.empty() or byte_at(back_coord()) == '\\n');\n\n    const StringView prefix = append_lines ?\n        StringView{} : m_lines[pos.line].substr(0, pos.column);\n    const StringView suffix = at_end ?\n        StringView{} : m_lines[pos.line].substr(pos.column);\n\n    LineList new_lines;\n    ByteCount start = 0;\n    for (ByteCount i = 0; i < content.length(); ++i)\n    {\n        if (content[i] == '\\n')\n        {\n            StringView line = content.substr(start, i + 1 - start);\n            new_lines.push_back(start == 0 ? StringData::create(prefix, line) : StringData::create(line));\n            start = i + 1;\n        }\n    }\n    if (start == 0)\n        new_lines.push_back(StringData::create(prefix, content, suffix));\n    else if (start != content.length() or not suffix.empty())\n        new_lines.push_back(StringData::create(content.substr(start), suffix));\n\n    auto line_it = m_lines.begin() + (int)pos.line;\n    auto new_lines_it = new_lines.begin();\n    if (not append_lines) // replace first line with new first line\n        *line_it++ = std::move(*new_lines_it++);\n\n    m_lines.insert(line_it,\n                   std::make_move_iterator(new_lines_it),\n                   std::make_move_iterator(new_lines.end()));\n\n    const LineCount last_line = pos.line + new_lines.size() - 1;\n    const auto end = at_end ? line_count()\n                            : BufferCoord{ last_line, m_lines[last_line].length() - suffix.length() };\n\n    m_changes.push_back({ Change::Insert, pos, end });\n    return {pos, end};\n}\n\nBufferCoord Buffer::do_erase(BufferCoord begin, BufferCoord end)\n{\n    if (begin == end)\n        return begin;\n\n    kak_assert(is_valid(begin));\n    kak_assert(is_valid(end));\n    StringView prefix = m_lines[begin.line].substr(0, begin.column);\n    StringView suffix = end.line == line_count() ? StringView{} : m_lines[end.line].substr(end.column);\n\n    auto new_line = (not prefix.empty() or not suffix.empty()) ? StringData::create(prefix, suffix) : StringDataPtr{};\n    m_lines.erase(m_lines.begin() + (int)begin.line, m_lines.begin() + (int)end.line);\n\n    m_changes.push_back({ Change::Erase, begin, end });\n    if (new_line)\n        m_lines.get_storage(begin.line) = std::move(new_line);\n\n    return begin;\n}\n\nvoid Buffer::apply_modification(const Modification& modification)\n{\n    StringView content = modification.content->strview();\n    BufferCoord coord = modification.coord;\n\n    kak_assert(is_valid(coord));\n    switch (modification.type)\n    {\n    case Modification::Insert:\n        do_insert(coord, content);\n        break;\n    case Modification::Erase:\n    {\n        auto end = advance(coord, content.length());\n        kak_assert(string(coord, end) == content);\n        do_erase(coord, end);\n        break;\n    }\n    default:\n        kak_assert(false);\n    }\n}\n\nBufferRange Buffer::insert(BufferCoord pos, StringView content)\n{\n    throw_if_read_only();\n\n    kak_assert(is_valid(pos));\n    if (content.empty())\n        return {pos, pos};\n\n    StringDataPtr real_content;\n    if (is_end(pos) and content.back() != '\\n')\n        real_content = intern(content + \"\\n\");\n    else\n        real_content = intern(content);\n\n    if (not (m_flags & Flags::NoUndo))\n        m_current_undo_group.push_back({Modification::Insert, pos, real_content});\n    return do_insert(pos, real_content->strview());\n}\n\nBufferCoord Buffer::erase(BufferCoord begin, BufferCoord end)\n{\n    throw_if_read_only();\n\n    kak_assert(is_valid(begin) and is_valid(end));\n    // do not erase last \\n except if we erase from the start of a line, and normalize\n    // end coord\n    if (is_end(end))\n        end = (begin.column != 0 or begin == BufferCoord{0,0}) ? prev(end) : end_coord();\n\n    if (begin >= end) // use >= to handle case where begin is {line_count}\n        return begin;\n\n    if (not (m_flags & Flags::NoUndo))\n        m_current_undo_group.push_back({Modification::Erase, begin,\n                                        intern(string(begin, end))});\n    return do_erase(begin, end);\n}\n\nBufferRange Buffer::replace(BufferCoord begin, BufferCoord end, StringView content)\n{\n    throw_if_read_only();\n\n    if (std::equal(iterator_at(begin), iterator_at(end), content.begin(), content.end()))\n        return {begin, end};\n\n    if (is_end(end) and not content.empty() and content.back() == '\\n')\n    {\n        auto pos = insert(erase(begin, back_coord()),\n                          content.substr(0, content.length() - 1)).begin;\n        return {pos, end_coord()};\n    }\n    return insert(erase(begin, end), content);\n}\n\nbool Buffer::is_modified() const\n{\n    return m_flags & Flags::File and\n           (m_history_id != m_last_save_history_id or\n            not m_current_undo_group.empty());\n}\n\nvoid Buffer::notify_saved(FsStatus status)\n{\n    if (not m_current_undo_group.empty())\n        commit_undo_group();\n\n    m_flags &= ~Flags::New;\n    m_last_save_history_id = m_history_id;\n    m_fs_status = status;\n}\n\nBufferCoord Buffer::advance(ArrayView<const StringDataPtr> lines, BufferCoord coord, ByteCount count)\n{\n    if (count > 0)\n    {\n        auto line = coord.line;\n        count += coord.column;\n        while (count >= lines[(size_t)line]->length)\n        {\n            count -= lines[(size_t)line++]->length;\n            if ((size_t)line == lines.size())\n                return line;\n        }\n        return { line, count };\n    }\n    else if (count < 0)\n    {\n        auto line = coord.line;\n        count += coord.column;\n        while (count < 0)\n        {\n            if (--line < 0)\n                return {0, 0};\n            count += lines[(size_t)line]->length;\n        }\n        return { line, count };\n    }\n    return coord;\n}\n\nBufferCoord Buffer::char_next(BufferCoord coord) const\n{\n    if (coord.column < m_lines[coord.line].length() - 1)\n    {\n        auto line = m_lines[coord.line];\n        auto column = coord.column + utf8::codepoint_size(line[coord.column]);\n        if (column >= line.length()) // Handle invalid utf-8\n            return { coord.line + 1, 0 };\n        return { coord.line, column };\n    }\n    return { coord.line + 1, 0 };\n}\n\nBufferCoord Buffer::char_prev(BufferCoord coord) const\n{\n    kak_assert(is_valid(coord));\n    if (coord.column == 0)\n        return { coord.line - 1, m_lines[coord.line - 1].length() - 1 };\n\n    auto line = m_lines[coord.line];\n    auto column = (int)(utf8::character_start(line.begin() + coord.column - 1, line.begin()) - line.begin());\n    return { coord.line, column };\n}\n\nvoid Buffer::set_fs_status(FsStatus status)\n{\n    kak_assert(m_flags & Flags::File);\n    m_fs_status = std::move(status);\n}\n\nconst FsStatus& Buffer::fs_status() const\n{\n    kak_assert(m_flags & Flags::File);\n    return m_fs_status;\n}\n\nvoid Buffer::on_option_changed(const Option& option)\n{\n    if (m_flags & Flags::NoBufSetOption)\n        return;\n\n    if (option.name() == \"readonly\")\n    {\n        if (option.get<bool>())\n            m_flags |= Flags::ReadOnly;\n        else\n            m_flags &= ~Flags::ReadOnly;\n    }\n    run_hook_in_own_context(Hook::BufSetOption,\n                            format(\"{}={}\", option.name(), option.get_desc_string()));\n}\n\nvoid Buffer::run_hook_in_own_context(Hook hook, StringView param, String client_name)\n{\n    if (m_flags & Buffer::Flags::NoHooks)\n        return;\n\n    InputHandler hook_handler{{ *this, Selection{} },\n                              Context::Flags::Draft,\n                              std::move(client_name)};\n    hooks().run_hook(hook, param, hook_handler.context());\n}\n\nOptional<BufferCoord> Buffer::last_modification_coord() const\n{\n    if (m_history_id == HistoryId::First)\n        return {};\n    return current_history_node().undo_group.back().coord;\n}\n\nString Buffer::debug_description() const\n{\n    size_t content_size = 0;\n    for (auto& line : m_lines)\n        content_size += (int)line->strview().length();\n\n    const size_t additional_size = accumulate(m_history, 0, [](size_t s, auto&& history) {\n            return sizeof(history) + history.undo_group.size() * sizeof(Modification) + s;\n        }) + m_changes.size() * sizeof(Change);\n\n    return format(\"{}\\nFlags: {}{}{}{}{}{}{}{}\\nUsed mem: content={} additional={}\\n\",\n                  display_name(),\n                  (m_flags & Flags::File) ? \"File (\" + filename() + \") \" : \"\",\n                  (m_flags & Flags::New) ? \"New \" : \"\",\n                  (m_flags & Flags::Fifo) ? \"Fifo \" : \"\",\n                  (m_flags & Flags::NoUndo) ? \"NoUndo \" : \"\",\n                  (m_flags & Flags::NoHooks) ? \"NoHooks \" : \"\",\n                  (m_flags & Flags::Debug) ? \"Debug \" : \"\",\n                  (m_flags & Flags::ReadOnly) ? \"ReadOnly \" : \"\",\n                  is_modified() ? \"Modified \" : \"\",\n                  content_size, additional_size);\n}\n\nUnitTest test_buffer{[]()\n{\n    auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create(lines)...}; };\n\n    Buffer empty_buffer(\"empty\", Buffer::Flags::None, make_lines(\"\\n\"));\n\n    Buffer buffer(\"test\", Buffer::Flags::None, make_lines(\"allo ?\\n\", \"mais que fais la police\\n\", \" hein ?\\n\", \" youpi\\n\"));\n    kak_assert(buffer.line_count() == 4);\n\n    BufferIterator pos = buffer.begin();\n    kak_assert(*pos == 'a');\n    pos += 6;\n    kak_assert(pos.coord() == BufferCoord{0, 6});\n    ++pos;\n    kak_assert(pos.coord() == BufferCoord{1, 0});\n    --pos;\n    kak_assert(pos.coord() == BufferCoord{0, 6});\n    pos += 1;\n    kak_assert(pos.coord() == BufferCoord{1, 0});\n    buffer.insert(pos.coord(), \"tchou kanaky\\n\");\n    kak_assert(buffer.line_count() == 5);\n    BufferIterator pos2 = buffer.end();\n    pos2 -= 9;\n    kak_assert(*pos2 == '?');\n\n    String str = buffer.string({ 4, 1 }, buffer.next({ 4, 5 }));\n    kak_assert(str == \"youpi\");\n\n    // check insert at end behaviour: auto add end of line if necessary\n    pos = buffer.end()-1;\n    buffer.insert(pos.coord(), \"tchou\");\n    kak_assert(buffer.string(pos.coord(), buffer.end_coord()) == \"tchou\\n\"_sv);\n\n    pos = buffer.end()-1;\n    buffer.insert(buffer.end_coord(), \"kanaky\\n\");\n    kak_assert(buffer.string((pos+1).coord(), buffer.end_coord()) == \"kanaky\\n\"_sv);\n\n    buffer.commit_undo_group();\n    buffer.erase((pos+1).coord(), buffer.end_coord());\n    buffer.insert(buffer.end_coord(), \"mutch\\n\");\n    buffer.commit_undo_group();\n    buffer.undo();\n    kak_assert(buffer.string(buffer.advance(buffer.end_coord(), -7), buffer.end_coord()) == \"kanaky\\n\"_sv);\n    buffer.redo();\n    kak_assert(buffer.string(buffer.advance(buffer.end_coord(), -6), buffer.end_coord()) == \"mutch\\n\"_sv);\n}};\n\nUnitTest test_undo{[]()\n{\n    auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create(lines)...}; };\n\n    Buffer buffer(\"test\", Buffer::Flags::None, make_lines(\"allo ?\\n\", \"mais que fais la police\\n\", \" hein ?\\n\", \" youpi\\n\"));\n    auto pos = buffer.end_coord();\n    buffer.insert(pos, \"kanaky\\n\");                           // change 1\n    buffer.commit_undo_group();\n    buffer.erase(pos, buffer.end_coord());                    // change 2\n    buffer.commit_undo_group();\n    buffer.insert(2_line, \"tchou\\n\");                         // change 3\n    buffer.commit_undo_group();\n    buffer.undo();\n    buffer.insert(2_line, \"mutch\\n\");                         // change 4\n    buffer.commit_undo_group();\n    buffer.erase({2, 1}, {2, 5});                             // change 5\n    buffer.commit_undo_group();\n    buffer.undo(2);\n    buffer.redo(2);\n    buffer.undo();\n    buffer.replace(2_line, buffer.end_coord(), \"foo\");        // change 6\n    buffer.commit_undo_group();\n\n    kak_assert((int)buffer.line_count() == 3);\n    kak_assert(buffer[0_line] == \"allo ?\\n\");\n    kak_assert(buffer[1_line] == \"mais que fais la police\\n\");\n    kak_assert(buffer[2_line] == \"foo\\n\");\n\n    buffer.move_to((Buffer::HistoryId)3);\n    kak_assert((int)buffer.line_count() == 5);\n    kak_assert(buffer[0_line] == \"allo ?\\n\");\n    kak_assert(buffer[1_line] == \"mais que fais la police\\n\");\n    kak_assert(buffer[2_line] == \"tchou\\n\");\n    kak_assert(buffer[3_line] == \" hein ?\\n\");\n    kak_assert(buffer[4_line] == \" youpi\\n\");\n\n    buffer.move_to((Buffer::HistoryId)4);\n    kak_assert((int)buffer.line_count() == 5);\n    kak_assert(buffer[0_line] == \"allo ?\\n\");\n    kak_assert(buffer[1_line] == \"mais que fais la police\\n\");\n    kak_assert(buffer[2_line] == \"mutch\\n\");\n    kak_assert(buffer[3_line] == \" hein ?\\n\");\n    kak_assert(buffer[4_line] == \" youpi\\n\");\n\n    buffer.move_to(Buffer::HistoryId::First);\n    kak_assert((int)buffer.line_count() == 4);\n    kak_assert(buffer[0_line] == \"allo ?\\n\");\n    kak_assert(buffer[1_line] == \"mais que fais la police\\n\");\n    kak_assert(buffer[2_line] == \" hein ?\\n\");\n    kak_assert(buffer[3_line] == \" youpi\\n\");\n    kak_assert(not buffer.undo());\n\n    buffer.move_to((Buffer::HistoryId)5);\n    kak_assert(not buffer.redo());\n\n    buffer.move_to((Buffer::HistoryId)6);\n    kak_assert(not buffer.redo());\n}};\n\n}\n"
  },
  {
    "path": "src/buffer.hh",
    "content": "#ifndef buffer_hh_INCLUDED\n#define buffer_hh_INCLUDED\n\n#include \"clock.hh\"\n#include \"coord.hh\"\n#include \"array.hh\"\n#include \"enum.hh\"\n#include \"file.hh\"\n#include \"flags.hh\"\n#include \"optional.hh\"\n#include \"option.hh\"\n#include \"range.hh\"\n#include \"safe_ptr.hh\"\n#include \"scope.hh\"\n#include \"shared_string.hh\"\n#include \"value.hh\"\n#include \"vector.hh\"\n\n#include <sys/types.h>\n#include <ctime>\n\nnamespace Kakoune\n{\n\nenum class Hook;\n\nenum class EolFormat\n{\n    Lf,\n    Crlf\n};\n\nconstexpr auto enum_desc(Meta::Type<EolFormat>)\n{\n    return make_array<EnumDesc<EolFormat>>({\n        { EolFormat::Lf, \"lf\" },\n        { EolFormat::Crlf, \"crlf\" },\n    });\n}\n\nenum class ByteOrderMark\n{\n    None,\n    Utf8\n};\n\nconstexpr auto enum_desc(Meta::Type<ByteOrderMark>)\n{\n    return make_array<EnumDesc<ByteOrderMark>>({\n        { ByteOrderMark::None, \"none\" },\n        { ByteOrderMark::Utf8, \"utf8\" },\n    });\n}\n\nenum class FinalEol\n{\n    Present,\n    Missing,\n    IfNotEmpty,\n};\n\nconstexpr auto enum_desc(Meta::Type<FinalEol>)\n{\n    return make_array<EnumDesc<FinalEol>>({\n        { FinalEol::Present, \"present\" },\n        { FinalEol::Missing, \"missing\" },\n        { FinalEol::IfNotEmpty, \"ifnotempty\" },\n    });\n}\n\nclass Buffer;\n\n// A BufferIterator permits to iterate over the characters of a buffer\nclass BufferIterator\n{\npublic:\n    using value_type = char;\n    using difference_type = ssize_t;\n    using pointer = const value_type*;\n    using reference = const value_type&;\n    // computing the distance between two iterator can be\n    // costly, so this is not strictly random access\n    using iterator_category = std::bidirectional_iterator_tag;\n\n    BufferIterator() = default;\n    BufferIterator(const Buffer& buffer, BufferCoord coord) noexcept;\n    BufferIterator(const StringDataPtr* lines, LineCount line_count, BufferCoord coord) noexcept;\n\n    bool operator== (const BufferIterator& iterator) const noexcept;\n    auto operator<=>(const BufferIterator& iterator) const noexcept;\n    bool operator== (const BufferCoord& coord) const noexcept;\n\n    const char& operator* () const noexcept;\n    const char& operator[](size_t n) const noexcept;\n    size_t operator- (const BufferIterator& iterator) const;\n\n    explicit operator bool() const { return m_lines; }\n\n    BufferIterator operator+ (ByteCount size) const;\n    BufferIterator operator- (ByteCount size) const;\n\n    BufferIterator& operator+= (ByteCount size);\n    BufferIterator& operator-= (ByteCount size);\n\n    BufferIterator& operator++ ();\n    BufferIterator& operator-- ();\n\n    BufferIterator operator++ (int);\n    BufferIterator operator-- (int);\n\n    const BufferCoord& coord() const noexcept { return m_coord; }\n    explicit operator BufferCoord() const noexcept { return m_coord; }\n    using Sentinel = BufferCoord;\n\nprivate:\n    const StringDataPtr* m_lines;\n    [[no_unique_address]] StringView m_line;\n    [[no_unique_address]] LineCount m_line_count;\n    BufferCoord m_coord;\n};\n\nusing BufferLines = Vector<StringDataPtr, MemoryDomain::BufferContent>;\nusing BufferRange = Range<BufferCoord>;\n\n// A Buffer is a in-memory representation of a file\n//\n// The Buffer class permits to read and mutate this file\n// representation. It also manage modifications undo/redo and\n// provides tools to deal with the line/column nature of text.\nclass Buffer final : public SafeCountable, public Scope, private OptionWatcher\n{\npublic:\n    enum class Flags\n    {\n        None     = 0,\n        File     = 1 << 0,\n        New      = 1 << 1,\n        Fifo     = 1 << 2,\n        NoUndo   = 1 << 3,\n        NoHooks  = 1 << 4,\n        Debug    = 1 << 5,\n        ReadOnly = 1 << 6,\n        NoBufSetOption = 1 << 7,\n        Locked = 1 << 8,\n    };\n    friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; }\n    friend class BufferIterator;\n\n    enum class HistoryId : size_t { First = 0, Invalid = (size_t)-1 };\n\n    Buffer(String name, Flags flags, BufferLines lines,\n           ByteOrderMark bom = ByteOrderMark::None,\n           EolFormat eolformat = EolFormat::Lf,\n           FinalEol = FinalEol::Present,\n           FsStatus fs_status = {InvalidTime, {}, {}});\n    Buffer(const Buffer&) = delete;\n    Buffer& operator= (const Buffer&) = delete;\n    ~Buffer();\n\n    Flags flags() const { return m_flags; }\n    Flags& flags() { return m_flags; }\n\n    bool set_name(String name);\n    void update_display_name();\n\n    BufferRange insert(BufferCoord pos, StringView content);\n    BufferCoord erase(BufferCoord begin, BufferCoord end);\n    BufferRange replace(BufferCoord begin, BufferCoord end, StringView content);\n\n    size_t          timestamp() const;\n    void            set_fs_status(FsStatus);\n    const FsStatus& fs_status() const;\n\n    void           commit_undo_group();\n    bool           undo(size_t count = 1);\n    bool           redo(size_t count = 1);\n    bool           move_to(HistoryId id);\n    HistoryId      current_history_id() const noexcept { return m_history_id; }\n    HistoryId      next_history_id() const noexcept { return (HistoryId)m_history.size(); }\n\n    String         string(BufferCoord begin, BufferCoord end) const;\n    StringView     substr(BufferCoord begin, BufferCoord end) const;\n\n    static BufferCoord advance(ArrayView<const StringDataPtr> lines, BufferCoord coord, ByteCount count);\n    static ByteCount   distance(ArrayView<const StringDataPtr> lines, BufferCoord begin, BufferCoord end);\n\n    const char&    byte_at(BufferCoord c) const;\n    ByteCount      distance(BufferCoord begin, BufferCoord end) const { return distance(m_lines, begin, end); }\n    BufferCoord    advance(BufferCoord coord, ByteCount count) const { return advance(m_lines, coord, count); }\n    BufferCoord    next(BufferCoord coord) const;\n    BufferCoord    prev(BufferCoord coord) const;\n\n    BufferCoord    char_next(BufferCoord coord) const;\n    BufferCoord    char_prev(BufferCoord coord) const;\n\n    BufferCoord    back_coord() const;\n    BufferCoord    end_coord() const;\n\n    bool           is_valid(BufferCoord c) const;\n    bool           is_end(BufferCoord c) const;\n\n    BufferIterator begin() const;\n    BufferIterator end() const;\n    LineCount      line_count() const;\n\n    Optional<BufferCoord> last_modification_coord() const;\n\n    StringView operator[](LineCount line) const\n    { return m_lines[line]; }\n\n    const StringDataPtr& line_storage(LineCount line) const\n    { return m_lines.get_storage(line); }\n\n    // returns an iterator at given coordinates. clamp line_and_column\n    BufferIterator iterator_at(BufferCoord coord) const;\n\n    // returns nearest valid coordinates from given ones\n    BufferCoord clamp(BufferCoord coord) const;\n\n    BufferCoord offset_coord(BufferCoord coord, CharCount offset, ColumnCount) const;\n    BufferCoordAndTarget offset_coord(BufferCoordAndTarget coord, LineCount offset, ColumnCount tabstop) const;\n\n    const String& name() const { return m_flags & Flags::File ? m_filename : m_display_name; }\n    const String& filename() const { return m_filename; }\n    const String& display_name() const { return m_display_name; }\n\n    // returns true if the buffer is in a different state than\n    // the last time it was saved\n    bool is_modified() const;\n\n    // notify the buffer that it was saved in the current state\n    void notify_saved(FsStatus status);\n\n    ValueMap& values() const { return m_values; }\n\n    void run_hook_in_own_context(Hook hook, StringView param,\n                                 String client_name = {});\n\n    void reload(BufferLines lines, ByteOrderMark bom, EolFormat eolformat, FinalEol finaleol, FsStatus status);\n\n    void check_invariant() const;\n\n    struct Change\n    {\n        enum Type : char { Insert, Erase };\n        Type type;\n        BufferCoord begin;\n        BufferCoord end;\n\n        #ifdef KAK_DEBUG\n        bool operator==(const Change&) const = default;\n        #endif\n    };\n    ConstArrayView<Change> changes_since(size_t timestamp) const;\n\n    String debug_description() const;\n\n    // Methods called by the buffer manager\n    void on_registered();\n    void on_unregistered();\n\n    void throw_if_read_only() const;\n\n    // A Modification holds a single atomic modification to Buffer\n    struct Modification\n    {\n        enum Type { Insert, Erase };\n\n        Type type;\n        BufferCoord coord;\n        StringDataPtr content;\n\n        Modification inverse() const;\n    };\n\n    using UndoGroup = Vector<Modification, MemoryDomain::BufferMeta>;\n\n    struct HistoryNode : UseMemoryDomain<MemoryDomain::BufferMeta>\n    {\n        HistoryNode(HistoryId parent);\n\n        HistoryId parent;\n        HistoryId redo_child = HistoryId::Invalid;\n        TimePoint committed;\n        UndoGroup undo_group;\n    };\n\n    const Vector<HistoryNode>& history() const { return m_history; }\n    const UndoGroup& current_undo_group() const { return m_current_undo_group; }\n\nprivate:\n    void on_option_changed(const Option& option) override;\n\n    BufferRange do_insert(BufferCoord pos, StringView content);\n    BufferCoord do_erase(BufferCoord begin, BufferCoord end);\n\n    void apply_modification(const Modification& modification);\n    void revert_modification(const Modification& modification);\n\n    struct LineList : BufferLines\n    {\n        [[gnu::always_inline]]\n        StringDataPtr& get_storage(LineCount line)\n        { return BufferLines::operator[]((int)line); }\n\n        [[gnu::always_inline]]\n        const StringDataPtr& get_storage(LineCount line) const\n        { return BufferLines::operator[]((int)line); }\n\n        [[gnu::always_inline]]\n        StringView operator[](LineCount line) const\n        { return get_storage(line)->strview(); }\n\n        StringView front() const { return BufferLines::front()->strview(); }\n        StringView back() const { return BufferLines::back()->strview(); }\n    };\n    LineList m_lines;\n\n    String m_filename;\n    String m_display_name;\n    Flags  m_flags;\n\n    Vector<HistoryNode> m_history;\n    HistoryId           m_history_id = HistoryId::Invalid;\n    HistoryId           m_last_save_history_id = HistoryId::Invalid;\n    UndoGroup           m_current_undo_group;\n\n          HistoryNode& history_node(HistoryId id)       { return m_history[(size_t)id]; }\n    const HistoryNode& history_node(HistoryId id) const { return m_history[(size_t)id]; }\n          HistoryNode& current_history_node()           { return m_history[(size_t)m_history_id]; }\n    const HistoryNode& current_history_node()     const { return m_history[(size_t)m_history_id]; }\n\n    Vector<Change, MemoryDomain::BufferMeta> m_changes;\n\n    FsStatus m_fs_status;\n\n    // Values are just data holding by the buffer, they are not part of its\n    // observable state\n    mutable ValueMap m_values;\n};\n\n}\n\n#include \"buffer.inl.hh\"\n\n#endif // buffer_hh_INCLUDED\n"
  },
  {
    "path": "src/buffer.inl.hh",
    "content": "#ifndef buffer_inl_h_INCLUDED\n#define buffer_inl_h_INCLUDED\n\n#include \"assert.hh\"\n\nnamespace Kakoune\n{\n\n[[gnu::always_inline]]\ninline const char& Buffer::byte_at(BufferCoord c) const\n{\n    kak_assert(c.line < line_count() and c.column < m_lines[c.line].length());\n    return m_lines[c.line][c.column];\n}\n\ninline BufferCoord Buffer::next(BufferCoord coord) const\n{\n    if (coord.column < m_lines[coord.line].length() - 1)\n        return {coord.line, coord.column + 1};\n    return { coord.line + 1, 0 };\n}\n\ninline BufferCoord Buffer::prev(BufferCoord coord) const\n{\n    if (coord.column == 0)\n        return { coord.line - 1, m_lines[coord.line - 1].length() - 1 };\n    return { coord.line, coord.column - 1 };\n}\n\ninline ByteCount Buffer::distance(ArrayView<const StringDataPtr> lines, BufferCoord begin, BufferCoord end)\n{\n    if (begin > end)\n        return -distance(lines, end, begin);\n    if (begin.line == end.line)\n        return end.column - begin.column;\n\n    ByteCount res = lines[(size_t)begin.line]->length - begin.column;\n    for (LineCount l = begin.line+1; l < end.line; ++l)\n        res += lines[(size_t)l]->length;\n    res += end.column;\n    return res;\n}\n\ninline bool Buffer::is_valid(BufferCoord c) const\n{\n    return (c.line >= 0 and c.column >= 0) and\n           ((c.line < line_count() and c.column < m_lines[c.line].length()) or\n            (c.line == line_count() and c.column == 0));\n}\n\ninline bool Buffer::is_end(BufferCoord c) const\n{\n    return c >= end_coord();\n}\n\ninline BufferIterator Buffer::begin() const\n{\n    return {*this, { 0, 0 }};\n}\n\ninline BufferIterator Buffer::end() const\n{\n    return {*this, end_coord()};\n}\n\n[[gnu::always_inline]]\ninline LineCount Buffer::line_count() const\n{\n    return LineCount{(int)m_lines.size()};\n}\n\ninline size_t Buffer::timestamp() const\n{\n    return m_changes.size();\n}\n\ninline StringView Buffer::substr(BufferCoord begin, BufferCoord end) const\n{\n    kak_assert(begin.line == end.line);\n    return m_lines[begin.line].substr(begin.column, end.column - begin.column);\n}\n\ninline ConstArrayView<Buffer::Change> Buffer::changes_since(size_t timestamp) const\n{\n    if (timestamp < m_changes.size())\n        return { m_changes.data() + timestamp,\n                 m_changes.data() + m_changes.size() };\n    return {};\n}\n\ninline BufferCoord Buffer::back_coord() const\n{\n    return { line_count() - 1, m_lines.back().length() - 1 };\n}\n\ninline BufferCoord Buffer::end_coord() const\n{\n    return line_count();\n}\n\ninline BufferIterator::BufferIterator(const Buffer& buffer, BufferCoord coord) noexcept\n    : BufferIterator{buffer.m_lines.data(), buffer.line_count(), coord} {}\n\ninline BufferIterator::BufferIterator(const StringDataPtr* lines, LineCount line_count, BufferCoord coord) noexcept\n    : m_lines{lines},\n      m_line{coord.line < line_count ? m_lines[(size_t)coord.line]->strview() : StringView{}},\n      m_line_count{line_count},\n      m_coord{coord} {}\n\ninline bool BufferIterator::operator==(const BufferIterator& iterator) const noexcept\n{\n    kak_assert(m_lines == iterator.m_lines);\n    return m_coord == iterator.m_coord;\n}\n\ninline auto BufferIterator::operator<=>(const BufferIterator& iterator) const noexcept\n{\n    kak_assert(m_lines == iterator.m_lines);\n    return (m_coord <=> iterator.m_coord);\n}\n\ninline bool BufferIterator::operator==(const BufferCoord& coord) const noexcept\n{\n    return m_coord == coord;\n}\n\n[[gnu::always_inline]]\ninline const char& BufferIterator::operator*() const noexcept\n{\n    return m_line[m_coord.column];\n}\n\ninline const char& BufferIterator::operator[](size_t n) const noexcept\n{\n    auto coord = Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, n);\n    return m_lines[(size_t)coord.line]->strview()[coord.column];\n}\n\ninline size_t BufferIterator::operator-(const BufferIterator& iterator) const\n{\n    kak_assert(m_lines == iterator.m_lines);\n    return (size_t)Buffer::distance({m_lines, (size_t)(int)m_line_count}, iterator.m_coord, m_coord);\n}\n\ninline BufferIterator BufferIterator::operator+(ByteCount size) const\n{\n    kak_assert(*this);\n    return { m_lines, m_line_count, Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, size) };\n}\n\ninline BufferIterator BufferIterator::operator-(ByteCount size) const\n{\n    return { m_lines, m_line_count, Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, -size) };\n}\n\ninline BufferIterator& BufferIterator::operator+=(ByteCount size)\n{\n    m_coord = Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, size);\n    m_line = m_lines[(size_t)m_coord.line]->strview();\n    return *this;\n}\n\ninline BufferIterator& BufferIterator::operator-=(ByteCount size)\n{\n    m_coord = Buffer::advance({m_lines, (size_t)(int)m_line_count}, m_coord, -size);\n    m_line = m_lines[(size_t)m_coord.line]->strview();\n    return *this;\n}\n\ninline BufferIterator& BufferIterator::operator++()\n{\n    if (++m_coord.column == m_line.length())\n    {\n        m_line = ((size_t)++m_coord.line < m_line_count) ?\n            m_lines[(size_t)m_coord.line]->strview() : StringView{};\n        m_coord.column = 0;\n    }\n    return *this;\n}\n\ninline BufferIterator& BufferIterator::operator--()\n{\n    if (m_coord.column == 0)\n    {\n        m_line = m_lines[(size_t)--m_coord.line]->strview();\n        m_coord.column = m_line.length() - 1;\n    }\n    else\n       --m_coord.column;\n    return *this;\n}\n\ninline BufferIterator BufferIterator::operator++(int)\n{\n    BufferIterator save = *this;\n    ++*this;\n    return save;\n}\n\ninline BufferIterator BufferIterator::operator--(int)\n{\n    BufferIterator save = *this;\n    --*this;\n    return save;\n}\n\n}\n#endif // buffer_inl_h_INCLUDED\n"
  },
  {
    "path": "src/buffer_manager.cc",
    "content": "#include \"buffer_manager.hh\"\n\n#include \"assert.hh\"\n#include \"buffer.hh\"\n#include \"client_manager.hh\"\n#include \"exception.hh\"\n#include \"format.hh\"\n#include \"buffer_utils.hh\"\n#include \"ranges.hh\"\n#include \"string.hh\"\n\nnamespace Kakoune\n{\n\nBufferManager::~BufferManager()\n{\n    // Move buffers to avoid running BufClose with buffers remaining in that list\n    BufferList buffers = std::move(m_buffers);\n\n    for (auto& buffer : buffers)\n        buffer->on_unregistered();\n\n    // Make sure not clients exists\n    if (ClientManager::has_instance())\n        ClientManager::instance().clear(true);\n}\n\nBuffer* BufferManager::create_buffer(String name, Buffer::Flags flags, BufferLines lines, ByteOrderMark bom, EolFormat eolformat, FinalEol finaleol, FsStatus fs_status)\n{\n    if (get_buffer_ifp(name) != nullptr)\n        throw runtime_error{\"buffer name is already in use\"};\n\n    m_buffers.push_back(make_unique_ptr<Buffer>(std::move(name), flags, std::move(lines), bom, eolformat, finaleol, fs_status));\n    auto* buffer = m_buffers.back().get();\n    buffer->on_registered();\n\n    if (contains(m_buffer_trash, buffer))\n        throw runtime_error{\"buffer got removed during its creation\"};\n\n    return buffer;\n}\n\nvoid BufferManager::delete_buffer(Buffer& buffer)\n{\n    if (buffer.flags() & Buffer::Flags::Locked)\n        throw runtime_error{\"Trying to delete a locked buffer\"};\n\n    auto it = find_if(m_buffers, [&](auto& p) { return p.get() == &buffer; });\n    if (it == m_buffers.end()) // we might be trying to recursively delete this buffer\n        return;\n\n    m_buffer_trash.emplace_back(std::move(*it));\n    m_buffers.erase(it);\n\n    if (ClientManager::has_instance())\n        ClientManager::instance().ensure_no_client_uses_buffer(buffer);\n\n    buffer.on_unregistered();\n}\n\nBuffer* BufferManager::get_buffer_ifp(StringView name)\n{\n    auto filename = real_path(parse_filename(name));\n    for (auto& buf : m_buffers)\n    {\n        if (buf->name() == name or\n            (buf->flags() & Buffer::Flags::File and buf->filename() == filename))\n            return buf.get();\n    }\n    return nullptr;\n}\n\nBuffer& BufferManager::get_buffer(StringView name)\n{\n    Buffer* res = get_buffer_ifp(name);\n    if (not res)\n        throw runtime_error{format(\"no such buffer '{}'\", name)};\n    return *res;\n}\n\nBuffer* BufferManager::get_buffer_matching_ifp(const FunctionRef<bool (Buffer&)>& filter)\n{\n    for (auto& buf : m_buffers | reverse())\n    {\n        if (filter(*buf))\n            return buf.get();\n    }\n    return nullptr;\n}\n\nBuffer& BufferManager::get_buffer_matching(const FunctionRef<bool (Buffer&)>& filter)\n{\n    Buffer* res = get_buffer_matching_ifp(filter);\n    if (not res)\n        throw runtime_error{format(\"no buffer found\")};\n    return *res;\n}\n\nBuffer& BufferManager::get_first_buffer()\n{\n    if (all_of(m_buffers, [](auto& b) { return (b->flags() & Buffer::Flags::Debug); }))\n        create_buffer(\"*scratch*\", Buffer::Flags::None, {StringData::create(\"\\n\")},\n                      ByteOrderMark::None, EolFormat::Lf, FinalEol::Present, {InvalidTime, {}, {}});\n\n    return *m_buffers.back();\n}\n\nvoid BufferManager::backup_modified_buffers()\n{\n    for (auto& buf : m_buffers)\n    {\n        if ((buf->flags() & Buffer::Flags::File) and buf->is_modified()\n            and not (buf->flags() & Buffer::Flags::ReadOnly))\n            write_buffer_to_backup_file(*buf);\n    }\n}\n\nvoid BufferManager::clear_buffer_trash()\n{\n    m_buffer_trash.clear();\n}\n\nvoid BufferManager::arrange_buffers(ConstArrayView<String> first_ones)\n{\n    Vector<size_t> indices;\n    for (const auto& name : first_ones)\n    {\n        auto filename = real_path(parse_filename(name));\n        auto it = find_if(m_buffers, [&](auto& buf) { return buf->display_name() == name or (buf->flags() & Buffer::Flags::File and buf->filename() == filename); });\n        if (it == m_buffers.end())\n            throw runtime_error{format(\"no such buffer '{}'\", name)};\n        size_t index = it - m_buffers.begin();\n        if (contains(indices, index))\n            throw runtime_error{format(\"buffer '{}' appears more than once\", name)};\n        indices.push_back(index);\n    }\n\n    BufferList res;\n    for (size_t index : indices)\n        res.push_back(std::move(m_buffers[index]));\n    for (auto& buf : m_buffers)\n    {\n        if (buf)\n            res.push_back(std::move(buf));\n    }\n    m_buffers = std::move(res);\n}\n\nvoid BufferManager::make_latest(Buffer& buffer)\n{\n    auto it = find(m_buffers, &buffer);\n    kak_assert(it != m_buffers.end());\n    std::rotate(it, it+1, m_buffers.end());\n}\n\n}\n"
  },
  {
    "path": "src/buffer_manager.hh",
    "content": "#ifndef buffer_manager_hh_INCLUDED\n#define buffer_manager_hh_INCLUDED\n\n#include \"buffer.hh\"\n#include \"vector.hh\"\n#include \"utils.hh\"\n#include \"unique_ptr.hh\"\n\nnamespace Kakoune\n{\n\nclass BufferManager : public Singleton<BufferManager>\n{\npublic:\n    using BufferList = Vector<UniquePtr<Buffer>, MemoryDomain::BufferMeta>;\n    using iterator = BufferList::const_iterator;\n\n    ~BufferManager();\n\n    Buffer* create_buffer(String name, Buffer::Flags flags, BufferLines lines, ByteOrderMark bom, EolFormat eolformat, FinalEol finaleol, FsStatus fs_status);\n\n    void delete_buffer(Buffer& buffer);\n\n    iterator begin() const { return m_buffers.cbegin(); }\n    iterator end() const { return m_buffers.cend(); }\n    size_t   count() const { return m_buffers.size(); }\n\n    Buffer* get_buffer_ifp(StringView name);\n    Buffer& get_buffer(StringView name);\n\n    Buffer* get_buffer_matching_ifp(const FunctionRef<bool (Buffer&)>& filter);\n    Buffer& get_buffer_matching(const FunctionRef<bool (Buffer&)>& filter);\n\n    void make_latest(Buffer& buffer);\n    void arrange_buffers(ConstArrayView<String> first_ones);\n\n    Buffer& get_first_buffer();\n\n    void backup_modified_buffers();\n\n    void clear_buffer_trash();\nprivate:\n    BufferList m_buffers;\n    BufferList m_buffer_trash;\n};\n\n}\n\n#endif // buffer_manager_hh_INCLUDED\n"
  },
  {
    "path": "src/buffer_utils.cc",
    "content": "#include \"buffer_utils.hh\"\n\n#include \"buffer_manager.hh\"\n#include \"hook_manager.hh\"\n#include \"coord.hh\"\n#include \"event_manager.hh\"\n#include \"option_manager.hh\"\n#include \"file.hh\"\n#include \"selection.hh\"\n#include \"changes.hh\"\n\n#include <unistd.h>\n\n#if defined(__APPLE__)\n#define st_mtim st_mtimespec\n#endif\n\n\nnamespace Kakoune\n{\n\nvoid replace(Buffer& buffer, ArrayView<BufferRange> ranges, ConstArrayView<String> strings)\n{\n    ForwardChangesTracker changes_tracker;\n    size_t timestamp  = buffer.timestamp();\n    for (size_t index = 0; index < ranges.size(); ++index)\n    {\n        auto& range = ranges[index];\n        range.begin = changes_tracker.get_new_coord_tolerant(range.begin);\n        range.end = changes_tracker.get_new_coord_tolerant(range.end);\n        kak_assert(buffer.is_valid(range.begin) and buffer.is_valid(range.end));\n\n        range = buffer.replace(range.begin, range.end, strings.empty() ? StringView{} : strings[std::min(index, strings.size()-1)]);\n        kak_assert(buffer.is_valid(range.begin) and buffer.is_valid(range.end));\n        changes_tracker.update(buffer, timestamp);\n    }\n\n    buffer.check_invariant();\n}\n\nColumnCount get_column(const Buffer& buffer,\n                       ColumnCount tabstop, BufferCoord coord)\n{\n    auto line = buffer[coord.line];\n    auto col = 0_col;\n    for (auto it = line.begin();\n         it != line.end() and coord.column > (int)(it - line.begin()); )\n    {\n        if (*it == '\\t')\n        {\n            col = (col / tabstop + 1) * tabstop;\n            ++it;\n        }\n        else\n            col += codepoint_width(utf8::read_codepoint(it, line.end()));\n    }\n    return col;\n}\n\nColumnCount column_length(const Buffer& buffer, ColumnCount tabstop, LineCount line)\n{\n    return get_column(buffer, tabstop, BufferCoord{line, ByteCount{INT_MAX}});\n}\n\nByteCount get_byte_to_column(const Buffer& buffer, ColumnCount tabstop, DisplayCoord coord)\n{\n    auto line = buffer[coord.line];\n    auto col = 0_col;\n    auto it = line.begin();\n    while (it != line.end() and coord.column > col)\n    {\n        if (*it == '\\t')\n        {\n            col = (col / tabstop + 1) * tabstop;\n            if (col > coord.column) // the target column was in the tab\n                break;\n            ++it;\n        }\n        else\n        {\n            auto next = it;\n            col += codepoint_width(utf8::read_codepoint(next, line.end()));\n            if (col > coord.column) // the target column was in the char\n                break;\n            it = next;\n        }\n    }\n    return (int)(it - line.begin());\n}\n\nstatic BufferLines parse_lines(const char* pos, const char* end, EolFormat eolformat, size_t expected_line_count = 0)\n{\n    BufferLines lines;\n    lines.reserve(expected_line_count);\n    while (pos < end)\n    {\n        if (lines.size() >= std::numeric_limits<int>::max())\n            throw runtime_error(\"too many lines\");\n\n        const char* eol = std::find(pos, end, '\\n');\n        if ((eol - pos) >= std::numeric_limits<int>::max())\n            throw runtime_error(\"line is too long\");\n\n        lines.emplace_back(StringData::create(StringView{pos, eol - (eolformat == EolFormat::Crlf and eol != end ? 1 : 0)}, \"\\n\"));\n        pos = eol + 1;\n    }\n\n    if (lines.empty())\n        lines.emplace_back(StringData::create(\"\\n\"));\n\n    return lines;\n}\n\nBuffer* create_buffer_from_string(String name, Buffer::Flags flags, StringView data)\n{\n    return BufferManager::instance().create_buffer(\n        std::move(name), flags,\n        parse_lines(data.begin(), data.end(), EolFormat::Lf),\n        ByteOrderMark::None, EolFormat::Lf, FinalEol::Present,\n        FsStatus{InvalidTime, {}, {}});\n}\n\ntemplate<typename Func>\ndecltype(auto) parse_file(StringView filename, Func&& func)\n{\n    MappedFile file{parse_filename(filename)};\n\n    const char* pos = file.data;\n    const char* end = pos + file.st.st_size;\n\n    auto bom = ByteOrderMark::None;\n    if (file.st.st_size >= 3 && StringView{pos, 3_byte} == \"\\xEF\\xBB\\xBF\")\n    {\n        bom = ByteOrderMark::Utf8;\n        pos += 3;\n    }\n\n    size_t line_count = 1;\n    bool has_crlf = false, has_lf = false;\n    for (auto it = std::find(pos, end, '\\n'); it != end; it = std::find(it+1, end, '\\n'), ++line_count)\n        ((it != pos and *(it-1) == '\\r') ? has_crlf : has_lf) = true;\n    auto eolformat = (has_crlf and not has_lf) ? EolFormat::Crlf : EolFormat::Lf;\n    auto finaleol = pos == end ? FinalEol::IfNotEmpty\n                               : *(end-1) == '\\n' ? FinalEol::Present : FinalEol::Missing;\n    FsStatus fs_status{file.st.st_mtim, file.st.st_size, murmur3(file.data, file.st.st_size)};\n    return func(parse_lines(pos, end, eolformat, line_count), bom, eolformat, finaleol, fs_status);\n}\n\nBuffer* open_file_buffer(StringView filename, Buffer::Flags flags)\n{\n    return parse_file(filename, [&](BufferLines&& lines, ByteOrderMark bom, EolFormat eolformat, FinalEol finaleol, FsStatus fs_status)  {\n        return BufferManager::instance().create_buffer(filename.str(), Buffer::Flags::File | flags,\n                                                       std::move(lines), bom, eolformat, finaleol, fs_status);\n    });\n}\n\nBuffer* open_or_create_file_buffer(StringView filename, Buffer::Flags flags)\n{\n    auto path = parse_filename(filename);\n    if (file_exists(path))\n        return open_file_buffer(filename.str(), Buffer::Flags::File | flags);\n    return create_buffer_from_string(filename.str(), Buffer::Flags::File | Buffer::Flags::New, StringView{});\n}\n\nvoid reload_file_buffer(Buffer& buffer)\n{\n    kak_assert(buffer.flags() & Buffer::Flags::File);\n    parse_file(buffer.filename(), [&](auto&&... params) {\n        buffer.reload(std::forward<decltype(params)>(params)...);\n    });\n    buffer.flags() &= ~Buffer::Flags::New;\n}\n\nvoid write_buffer_to_fd(Buffer& buffer, int fd)\n{\n    auto eolformat = buffer.options()[\"eolformat\"].get<EolFormat>();\n    StringView eoldata;\n    if (eolformat == EolFormat::Crlf)\n        eoldata = \"\\r\\n\";\n    else\n        eoldata = \"\\n\";\n    auto finaleol = buffer.options()[\"finaleol\"].get<FinalEol>();\n    bool write_eol_at_eof = finaleol == FinalEol::Present or\n                           (finaleol == FinalEol::IfNotEmpty and (buffer.line_count() != 1 or buffer[0] != \"\\n\"));\n\n    BufferedWriter<false> writer{fd};\n    if (buffer.options()[\"BOM\"].get<ByteOrderMark>() == ByteOrderMark::Utf8)\n        writer.write(\"\\xEF\\xBB\\xBF\");\n\n    for (LineCount i = 0; i < buffer.line_count(); ++i)\n    {\n        // end of lines are written according to eolformat but always\n        // stored as \\n\n        StringView linedata = buffer[i];\n        writer.write(linedata.substr(0, linedata.length()-1));\n        if (write_eol_at_eof or i != buffer.line_count()-1)\n            writer.write(eoldata);\n    }\n}\n\nvoid write_buffer_to_file(Buffer& buffer, StringView filename,\n                          WriteMethod method, WriteFlags flags)\n{\n    auto zfilename = filename.zstr();\n    struct stat st;\n\n    bool replace = method == WriteMethod::Replace;\n    bool force = flags & WriteFlags::Force;\n\n    if ((replace or force) and (::stat(zfilename, &st) != 0 or\n                                (st.st_mode & S_IFMT) != S_IFREG))\n    {\n        force = false;\n        replace = false;\n    }\n\n    if (force and ::chmod(zfilename, st.st_mode | S_IWUSR) < 0)\n        throw runtime_error(format(\"unable to change file permissions: {}\", strerror(errno)));\n\n    char temp_filename[PATH_MAX];\n    const int fd = replace ? open_temp_file(filename, temp_filename)\n                           : create_file(zfilename);\n    if (fd == -1)\n    {\n        auto saved_errno = errno;\n        if (force)\n            ::chmod(zfilename, st.st_mode);\n        throw file_access_error(filename, strerror(saved_errno));\n    }\n\n    {\n        auto close_fd = OnScopeEnd([fd]{ close(fd); });\n        write_buffer_to_fd(buffer, fd);\n        if (flags & WriteFlags::Sync)\n            ::fsync(fd);\n    }\n\n    if (replace and geteuid() == 0 and ::chown(temp_filename, st.st_uid, st.st_gid) < 0)\n        throw runtime_error(format(\"unable to set replacement file ownership: {}\", strerror(errno)));\n    if (replace and ::chmod(temp_filename, st.st_mode) < 0)\n        throw runtime_error(format(\"unable to set replacement file permissions: {}\", strerror(errno)));\n    if (force and not replace and ::chmod(zfilename, st.st_mode) < 0)\n        throw runtime_error(format(\"unable to restore file permissions: {}\", strerror(errno)));\n\n    if (replace and rename(temp_filename, zfilename) != 0)\n    {\n        if (force)\n            ::chmod(zfilename, st.st_mode);\n        throw runtime_error(\"replacing file failed\");\n    }\n\n    if ((buffer.flags() & Buffer::Flags::File) and\n        real_path(filename) == real_path(buffer.filename()))\n        buffer.notify_saved(get_fs_status(real_path(filename)));\n}\n\nvoid write_buffer_to_backup_file(Buffer& buffer)\n{\n    const int fd = open_temp_file(buffer.filename());\n    if (fd >= 0)\n    {\n        write_buffer_to_fd(buffer, fd);\n        close(fd);\n    }\n}\n\nBuffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, AutoScroll scroll)\n{\n    static ValueId fifo_watcher_id = get_free_value_id();\n\n    auto& buffer_manager = BufferManager::instance();\n    Buffer* buffer = buffer_manager.get_buffer_ifp(name);\n    if (buffer)\n    {\n        buffer->flags() |= Buffer::Flags::NoUndo | flags;\n        buffer->values().clear();\n        buffer->reload({StringData::create(\"\\n\")}, ByteOrderMark::None, EolFormat::Lf, FinalEol::Present, {InvalidTime, {}, {}});\n        buffer_manager.make_latest(*buffer);\n    }\n    else\n        buffer = buffer_manager.create_buffer(\n            std::move(name), flags | Buffer::Flags::Fifo | Buffer::Flags::NoUndo,\n            {StringData::create(\"\\n\")}, ByteOrderMark::None, EolFormat::Lf, FinalEol::Present, {InvalidTime, {}, {}});\n\n    struct FifoWatcher : FDWatcher\n    {\n        FifoWatcher(int fd, Buffer& buffer, AutoScroll scroll)\n            : FDWatcher(fd, FdEvents::Read, EventMode::Normal,\n                        [](FDWatcher& watcher, FdEvents, EventMode mode) {\n                            if (mode == EventMode::Normal)\n                                static_cast<FifoWatcher&>(watcher).read_fifo();\n                        }),\n              m_buffer(buffer), m_scroll(scroll)\n        {}\n\n        ~FifoWatcher()\n        {\n            kak_assert(m_buffer.flags() & Buffer::Flags::Fifo);\n            close_fd();\n            m_buffer.run_hook_in_own_context(Hook::BufCloseFifo, \"\");\n            if (not m_buffer.values().contains(fifo_watcher_id))\n                m_buffer.flags() &= ~(Buffer::Flags::Fifo | Buffer::Flags::NoUndo);\n        }\n\n        void read_fifo()\n        {\n            kak_assert(m_buffer.flags() & Buffer::Flags::Fifo);\n\n            constexpr size_t buffer_size = 2048;\n            // if we read data slower than it arrives in the fifo, limiting the\n            // iteration number allows us to go back go back to the event loop and\n            // handle other events sources (such as input)\n            constexpr size_t max_loop = 1024;\n            bool closed = false;\n            size_t loop = 0;\n            char data[buffer_size];\n            Optional<BufferCoord> insert_begin;\n            const int fifo = fd();\n\n            {\n                auto restore_flags = OnScopeEnd([this, flags=m_buffer.flags()] { m_buffer.flags() = flags; });\n                m_buffer.flags() &= ~Buffer::Flags::ReadOnly;\n                do\n                {\n\n                    const ssize_t count = ::read(fifo, data, buffer_size);\n                    if (count <= 0)\n                    {\n                        closed = true;\n                        break;\n                    }\n\n                    auto pos = m_buffer.back_coord();\n                    const bool is_first = pos == BufferCoord{0,0};\n                    if ((m_scroll == AutoScroll::No and (is_first or m_had_trailing_newline))\n                        or (m_scroll == AutoScroll::NotInitially and is_first))\n                        pos = m_buffer.next(pos);\n\n                    auto inserted_range = m_buffer.insert(pos, StringView(data, data+count));\n                    if (not insert_begin)\n                        insert_begin = inserted_range.begin;\n                    pos = inserted_range.end;\n\n                    bool have_trailing_newline = (data[count-1] == '\\n');\n                    if (m_scroll != AutoScroll::Yes)\n                    {\n                        if (is_first)\n                        {\n                            m_buffer.erase({0,0}, m_buffer.next({0,0}));\n                            --insert_begin->line;\n                            if (m_scroll == AutoScroll::NotInitially and have_trailing_newline)\n                                m_buffer.insert(m_buffer.end_coord(), \"\\n\");\n                        }\n                        else if (m_scroll == AutoScroll::No and\n                                 not m_had_trailing_newline and have_trailing_newline)\n                            m_buffer.erase(m_buffer.prev(pos), pos);\n                    }\n                    m_had_trailing_newline = have_trailing_newline;\n                }\n                while (++loop < max_loop  and fd_readable(fifo));\n            }\n\n            if (insert_begin)\n            {\n                auto insert_back = (m_had_trailing_newline and m_scroll == AutoScroll::No)\n                                 ? m_buffer.back_coord() : m_buffer.prev(m_buffer.back_coord());\n                m_buffer.run_hook_in_own_context(\n                    Hook::BufReadFifo,\n                    selection_to_string(ColumnType::Byte, m_buffer, {*insert_begin, insert_back}));\n            }\n\n            if (closed)\n                m_buffer.values().erase(fifo_watcher_id); // will delete this\n        }\n\n        Buffer& m_buffer;\n        AutoScroll m_scroll;\n        bool m_had_trailing_newline = false;\n    };\n\n    buffer->values()[fifo_watcher_id] = Value(Meta::Type<FifoWatcher>{}, fd, *buffer, scroll);\n    buffer->flags() = flags | Buffer::Flags::Fifo | Buffer::Flags::NoUndo;\n    buffer->run_hook_in_own_context(Hook::BufOpenFifo, buffer->name());\n\n    return buffer;\n}\n\nauto to_string(Buffer::HistoryId id)\n{\n    using Result = decltype(to_string(size_t{}));\n    if (id == Buffer::HistoryId::Invalid)\n        return Result{1, \"-\"};\n    return to_string(static_cast<size_t>(id));\n}\n\nstatic String modification_as_string(const Buffer::Modification& modification)\n{\n    return format(\"{}{}.{}|{}\",\n                  modification.type == Buffer::Modification::Type::Insert ? '+' : '-',\n                  modification.coord.line, modification.coord.column,\n                  modification.content->strview());\n}\n\nVector<String> history_as_strings(ConstArrayView<Buffer::HistoryNode> history)\n{\n    Vector<String> res;\n    for (auto& node : history)\n    {\n        auto seconds = std::chrono::duration_cast<std::chrono::seconds>(node.committed.time_since_epoch());\n        res.push_back(to_string(node.parent));\n        res.push_back(to_string(seconds.count()));\n        res.push_back(to_string(node.redo_child));\n        for (auto& modification : node.undo_group)\n            res.push_back(modification_as_string(modification));\n    };\n    return res;\n}\n\nVector<String> undo_group_as_strings(const Buffer::UndoGroup& undo_group)\n{\n    Vector<String> res;\n    for (auto& modification : undo_group)\n        res.push_back(modification_as_string(modification));\n    return res;\n}\n\nString generate_buffer_name(StringView pattern)\n{\n    auto& buffer_manager = BufferManager::instance();\n    for (int i = 0; true; ++i)\n    {\n        String name = format(pattern, i);\n        if (buffer_manager.get_buffer_ifp(name) == nullptr)\n            return name;\n    }\n}\n\n}\n"
  },
  {
    "path": "src/buffer_utils.hh",
    "content": "#ifndef buffer_utils_hh_INCLUDED\n#define buffer_utils_hh_INCLUDED\n\n#include \"buffer.hh\"\n#include \"selection.hh\"\n\n#include \"utf8_iterator.hh\"\n#include \"unicode.hh\"\n\nnamespace Kakoune\n{\n\ninline String content(const Buffer& buffer, const Selection& range)\n{\n    return buffer.string(range.min(), buffer.char_next(range.max()));\n}\n\ninline BufferCoord erase(Buffer& buffer, const Selection& range)\n{\n    return buffer.erase(range.min(), buffer.char_next(range.max()));\n}\n\nvoid replace(Buffer& buffer, ArrayView<BufferRange> ranges, ConstArrayView<String> strings);\n\ninline CharCount char_length(const Buffer& buffer, const Selection& range)\n{\n    return utf8::distance(buffer.iterator_at(range.min()),\n                          buffer.iterator_at(buffer.char_next(range.max())));\n}\n\ninline CharCount char_length(const Buffer& buffer, const BufferCoord& begin, const BufferCoord& end)\n{\n    return utf8::distance(buffer.iterator_at(begin), buffer.iterator_at(end));\n}\n\ninline ColumnCount column_length(const Buffer& buffer, const BufferCoord& begin, const BufferCoord& end)\n{\n    return utf8::column_distance(buffer.iterator_at(begin), buffer.iterator_at(end));\n}\n\ninline bool is_bol(BufferCoord coord)\n{\n    return coord.column == 0;\n}\n\ninline bool is_eol(const Buffer& buffer, BufferCoord coord)\n{\n    return buffer.is_end(coord) or buffer[coord.line].length() == coord.column+1;\n}\n\ninline bool is_bow(const Buffer& buffer, BufferCoord coord)\n{\n    auto it = utf8::iterator<BufferIterator>(buffer.iterator_at(coord), buffer);\n    if (coord == BufferCoord{0,0})\n        return is_word(*it);\n\n    return not is_word(*(it-1)) and is_word(*it);\n}\n\ninline bool is_eow(const Buffer& buffer, BufferCoord coord)\n{\n    if (buffer.is_end(coord) or coord == BufferCoord{0,0})\n        return false;\n\n    auto it = utf8::iterator<BufferIterator>(buffer.iterator_at(coord), buffer);\n    return is_word(*(it-1)) and not is_word(*it);\n}\n\nColumnCount get_column(const Buffer& buffer, ColumnCount tabstop, BufferCoord coord);\nColumnCount column_length(const Buffer& buffer, ColumnCount tabstop, LineCount line);\n\nByteCount get_byte_to_column(const Buffer& buffer, ColumnCount tabstop,\n                             DisplayCoord coord);\n\nenum class AutoScroll { No, NotInitially, Yes };\nBuffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, AutoScroll scroll);\nBuffer* create_buffer_from_string(String name, Buffer::Flags flags, StringView data);\nBuffer* open_file_buffer(StringView filename,\n                         Buffer::Flags flags = Buffer::Flags::None);\nBuffer* open_or_create_file_buffer(StringView filename,\n                                   Buffer::Flags flags = Buffer::Flags::None);\nvoid reload_file_buffer(Buffer& buffer);\n\nenum class WriteFlags\n{\n    None  = 0,\n    Force = 0b01,\n    Sync  = 0b10\n};\nconstexpr bool with_bit_ops(Meta::Type<WriteFlags>) { return true; }\n\nvoid write_buffer_to_file(Buffer& buffer, StringView filename,\n                          WriteMethod method, WriteFlags flags);\nvoid write_buffer_to_fd(Buffer& buffer, int fd);\nvoid write_buffer_to_backup_file(Buffer& buffer);\n\n\nvoid write_to_debug_buffer(StringView str);\n\nVector<String> history_as_strings(ConstArrayView<Buffer::HistoryNode> history);\nVector<String> undo_group_as_strings(const Buffer::UndoGroup& undo_group);\n\nString generate_buffer_name(StringView pattern);\n\n}\n\n#endif // buffer_utils_hh_INCLUDED\n"
  },
  {
    "path": "src/changes.cc",
    "content": "#include \"changes.hh\"\n\nnamespace Kakoune\n{\nvoid ForwardChangesTracker::update(const Buffer::Change& change)\n{\n    kak_assert(change.begin >= cur_pos);\n\n    if (change.type == Buffer::Change::Insert)\n    {\n        old_pos = get_old_coord(change.begin);\n        cur_pos = change.end;\n    }\n    else if (change.type == Buffer::Change::Erase)\n    {\n        old_pos = get_old_coord(change.end);\n        cur_pos = change.begin;\n    }\n}\n\nvoid ForwardChangesTracker::update(const Buffer& buffer, size_t& timestamp)\n{\n    for (auto& change : buffer.changes_since(timestamp))\n        update(change);\n    timestamp = buffer.timestamp();\n}\n\nBufferCoord ForwardChangesTracker::get_old_coord(BufferCoord coord) const\n{\n    kak_assert(cur_pos <= coord);\n    auto pos_change = cur_pos - old_pos;\n    if (cur_pos.line == coord.line)\n    {\n        kak_assert(pos_change.column <= coord.column);\n        coord.column -= pos_change.column;\n    }\n    coord.line -= pos_change.line;\n    kak_assert(old_pos <= coord);\n    return coord;\n}\n\nBufferCoord ForwardChangesTracker::get_new_coord(BufferCoord coord) const\n{\n    kak_assert(old_pos <= coord);\n    auto pos_change = cur_pos - old_pos;\n    if (old_pos.line == coord.line)\n    {\n        kak_assert(-pos_change.column <= coord.column);\n        coord.column += pos_change.column;\n    }\n    coord.line += pos_change.line;\n    kak_assert(cur_pos <= coord);\n    return coord;\n}\n\nBufferCoord ForwardChangesTracker::get_new_coord_tolerant(BufferCoord coord) const\n{\n    if (coord < old_pos)\n        return cur_pos;\n    return get_new_coord(coord);\n}\n\nbool ForwardChangesTracker::relevant(const Buffer::Change& change, BufferCoord old_coord) const\n{\n    auto new_coord = get_new_coord_tolerant(old_coord);\n    return change.type == Buffer::Change::Insert ? change.begin <= new_coord\n                                                 : change.begin < new_coord;\n}\n\nconst Buffer::Change* forward_sorted_until(const Buffer::Change* first, const Buffer::Change* last)\n{\n    if (first != last) {\n        const Buffer::Change* next = first;\n        while (++next != last) {\n            const auto& ref = first->type == Buffer::Change::Insert ? first->end : first->begin;\n            if (next->begin <= ref)\n                return next;\n            first = next;\n        }\n    }\n    return last;\n}\n\nconst Buffer::Change* backward_sorted_until(const Buffer::Change* first, const Buffer::Change* last)\n{\n    if (first != last) {\n        const Buffer::Change* next = first;\n        while (++next != last) {\n            if (first->begin < next->end)\n                return next;\n            first = next;\n        }\n    }\n    return last;\n}\n\n}\n"
  },
  {
    "path": "src/changes.hh",
    "content": "#ifndef changes_hh_INCLUDED\n#define changes_hh_INCLUDED\n\n#include \"buffer.hh\"\n#include \"coord.hh\"\n\nnamespace Kakoune\n{\n\n// This tracks position changes for changes that are done\n// in a forward way (each change takes place at a position)\n// *after* the previous one.\nstruct ForwardChangesTracker\n{\n    BufferCoord cur_pos; // last change position at current modification\n    BufferCoord old_pos; // last change position at start\n\n    void update(const Buffer::Change& change);\n    void update(const Buffer& buffer, size_t& timestamp);\n\n    BufferCoord get_old_coord(BufferCoord coord) const;\n    BufferCoord get_new_coord(BufferCoord coord) const;\n    BufferCoord get_new_coord_tolerant(BufferCoord coord) const;\n\n    bool relevant(const Buffer::Change& change, BufferCoord old_coord) const;\n};\n\nconst Buffer::Change* forward_sorted_until(const Buffer::Change* first, const Buffer::Change* last);\nconst Buffer::Change* backward_sorted_until(const Buffer::Change* first, const Buffer::Change* last);\n\ntemplate<typename Range, typename AdvanceFunc>\nauto update_range(ForwardChangesTracker& changes_tracker, Range& range, AdvanceFunc&& advance_while_relevant)\n{\n    auto& first = get_first(range);\n    auto& last = get_last(range);\n    advance_while_relevant(first);\n    first = changes_tracker.get_new_coord_tolerant(first);\n\n    if (last < BufferCoord{0,0})\n        return;\n\n    advance_while_relevant(last);\n    last = changes_tracker.get_new_coord_tolerant(last);\n}\n\ntemplate<typename RangeContainer>\nvoid update_forward(ConstArrayView<Buffer::Change> changes, RangeContainer& ranges)\n{\n    ForwardChangesTracker changes_tracker;\n    auto advance_while_relevant = [&, it = changes.begin()]\n                                  (const BufferCoord& pos) mutable {\n        while (it != changes.end() and changes_tracker.relevant(*it, pos))\n            changes_tracker.update(*it++);\n    };\n\n    auto range_it = std::lower_bound(ranges.begin(), ranges.end(), changes.front(),\n                                     [](auto& range, const Buffer::Change& change) { return get_last(range) < change.begin; });\n    for (auto end = ranges.end(); range_it != end; ++range_it)\n        update_range(changes_tracker, *range_it, advance_while_relevant);\n}\n\ntemplate<typename RangeContainer>\nvoid update_backward(ConstArrayView<Buffer::Change> changes, RangeContainer& ranges)\n{\n    ForwardChangesTracker changes_tracker;\n    auto advance_while_relevant = [&, it = changes.rbegin()]\n                                  (const BufferCoord& pos) mutable {\n        while (it != changes.rend())\n        {\n            const Buffer::Change change{it->type,\n                                        changes_tracker.get_new_coord(it->begin),\n                                        changes_tracker.get_new_coord(it->end)};\n            if (not changes_tracker.relevant(change, pos))\n                break;\n            changes_tracker.update(change);\n            ++it;\n        }\n    };\n\n    for (auto& range : ranges)\n        update_range(changes_tracker, range, advance_while_relevant);\n}\n\ntemplate<typename RangeContainer>\nvoid update_ranges(Buffer& buffer, size_t timestamp, RangeContainer&& ranges)\n{\n    if (timestamp == buffer.timestamp())\n        return;\n\n    auto changes = buffer.changes_since(timestamp);\n    for (auto change_it = changes.begin(); change_it != changes.end(); )\n    {\n        auto forward_end = forward_sorted_until(change_it, changes.end());\n        auto backward_end = backward_sorted_until(change_it, changes.end());\n\n        if (forward_end >= backward_end)\n        {\n            update_forward({ change_it, forward_end }, ranges);\n            change_it = forward_end;\n        }\n        else\n        {\n            update_backward({ change_it, backward_end }, ranges);\n            change_it = backward_end;\n        }\n    }\n}\n\n}\n\n#endif // changes_hh_INCLUDED\n"
  },
  {
    "path": "src/client.cc",
    "content": "#include \"client.hh\"\n\n#include \"clock.hh\"\n#include \"context.hh\"\n#include \"buffer_utils.hh\"\n#include \"debug.hh\"\n#include \"file.hh\"\n#include \"remote.hh\"\n#include \"option.hh\"\n#include \"option_types.hh\"\n#include \"client_manager.hh\"\n#include \"event_manager.hh\"\n#include \"hook_manager.hh\"\n#include \"keymap_manager.hh\"\n#include \"option_manager.hh\"\n#include \"shell_manager.hh\"\n#include \"face_registry.hh\"\n#include \"command_manager.hh\"\n#include \"user_interface.hh\"\n#include \"window.hh\"\n#include \"hash_map.hh\"\n\n#include <csignal>\n#include <unistd.h>\n\n#include <utility>\n\nnamespace Kakoune\n{\n\nClient::Client(UniquePtr<UserInterface>&& ui,\n               UniquePtr<Window>&& window,\n               SelectionList selections, int pid,\n               EnvVarMap env_vars,\n               String name,\n               OnExitCallback on_exit)\n    : m_ui{std::move(ui)}, m_window{std::move(window)},\n      m_pid{pid},\n      m_on_exit{std::move(on_exit)},\n      m_env_vars(std::move(env_vars)),\n      m_input_handler{std::move(selections), Context::Flags::None,\n                      std::move(name)}\n{\n    m_window->set_client(this);\n\n    context().set_client(*this);\n    context().set_window(*m_window);\n\n    m_window->set_dimensions(m_ui->dimensions());\n    m_window->options().register_watcher(*this);\n\n    m_ui->set_ui_options(m_window->options()[\"ui_options\"].get<UserInterface::Options>());\n    m_ui->set_on_key([this](Key key) {\n        kak_assert(key != Key::Invalid);\n        if (key == ctrl('c'))\n        {\n            auto prev_handler = set_signal_handler(SIGINT, SIG_IGN);\n            killpg(getpgrp(), SIGINT);\n            set_signal_handler(SIGINT, prev_handler);\n        }\n        else if (key == ctrl('g'))\n        {\n            m_pending_keys.clear();\n            print_status({}, {\"operation cancelled\", context().faces()[\"Error\"]}, -1);\n            throw cancel{};\n        }\n        else if (key.modifiers & Key::Modifiers::Resize)\n        {\n            m_window->set_dimensions(key.coord());\n            force_redraw(true);\n        }\n        else\n            m_pending_keys.push_back(key);\n    });\n    m_ui->set_on_paste([this](StringView content) {\n        context().input_handler().paste(content);\n    });\n\n    m_window->hooks().run_hook(Hook::WinDisplay, m_window->buffer().name(), context());\n\n    force_redraw();\n}\n\nClient::~Client()\n{\n    m_window->options().unregister_watcher(*this);\n    m_window->set_client(nullptr);\n    // Do not move the selections here, as we need them to be valid\n    // in order to correctly destroy the input handler\n    ClientManager::instance().add_free_window(std::move(m_window),\n                                              context().selections());\n}\n\nbool Client::is_ui_ok() const\n{\n    return m_ui->is_ok();\n}\n\nbool Client::process_pending_inputs()\n{\n    const bool debug_keys = (bool)(context().options()[\"debug\"].get<DebugFlags>() & DebugFlags::Keys);\n    m_window->run_resize_hook_ifn();\n    // steal keys as we might receive new keys while handling them.\n    Vector<Key, MemoryDomain::Client> keys = std::move(m_pending_keys);\n    for (auto& key : keys)\n    {\n        try\n        {\n            if (debug_keys)\n                write_to_debug_buffer(format(\"Client '{}' got key '{}'\", context().name(), key));\n\n            if (key == Key::FocusIn)\n                context().hooks().run_hook(Hook::FocusIn, context().name(), context());\n            else if (key == Key::FocusOut)\n                context().hooks().run_hook(Hook::FocusOut, context().name(), context());\n            else\n            {\n                context().ensure_cursor_visible = true;\n                m_input_handler.handle_key(key, /*synthesized=*/false);\n            }\n\n            context().hooks().run_hook(Hook::RawKey, to_string(key), context());\n        }\n        catch (Kakoune::runtime_error& error)\n        {\n            write_to_debug_buffer(format(\"Error: {}\", error.what()));\n            context().print_status({}, {error.what().str(), context().faces()[\"Error\"]}, -1);\n            context().hooks().run_hook(Hook::RuntimeError, error.what(), context());\n        }\n    }\n    return not keys.empty();\n}\n\nvoid Client::print_status(DisplayLine prompt, DisplayLine content, ColumnCount cursor_pos)\n{\n    m_status_prompt = std::move(prompt);\n    m_status_content = std::move(content);\n    m_status_cursor_pos = cursor_pos;\n    m_ui_pending |= StatusLine;\n    m_pending_clear &= ~PendingClear::StatusLine;\n}\n\n\nDisplayCoord Client::dimensions() const\n{\n    return m_ui->dimensions();\n}\n\nString generate_context_info(const Context& context)\n{\n    String s = \"\";\n    if (context.buffer().is_modified())\n        s += \"[+]\";\n    if (context.client().input_handler().is_recording())\n        s += format(\"[recording ({})]\", context.client().input_handler().recording_reg());\n    if (context.hooks_disabled())\n        s += \"[no-hooks]\";\n    if (not(context.buffer().flags() & (Buffer::Flags::File | Buffer::Flags::Debug)))\n        s += \"[scratch]\";\n    if (context.buffer().flags() & Buffer::Flags::New)\n        s += \"[new file]\";\n    if (context.buffer().flags() & Buffer::Flags::Fifo)\n        s += \"[fifo]\";\n    if (context.buffer().flags() & Buffer::Flags::Debug)\n        s += \"[debug]\";\n    if (context.buffer().flags() & Buffer::Flags::ReadOnly)\n        s += \"[readonly]\";\n    return s;\n}\n\nDisplayLine Client::generate_mode_line() const\n{\n    DisplayLine modeline;\n    try\n    {\n        auto [mode_info_line, normal_params] = context().client().input_handler().mode_info();\n        const String& modelinefmt = context().options()[\"modelinefmt\"].get<String>();\n        HashMap<String, DisplayLine> atoms{{ \"mode_info\",  mode_info_line},\n                                           { \"context_info\", {generate_context_info(context()),\n                                                              context().faces()[\"Information\"]}}};\n        ShellContext shell_context{{}, {\n            {\"register\", normal_params ? StringView{normal_params->reg}.str() : \"\"},\n            {\"count\", normal_params ? String{to_string(normal_params->count)} : \"\"},\n        }};\n        auto expanded = expand(modelinefmt, context(), shell_context,\n                               [](String s) { return escape(s, '{', '\\\\'); });\n        modeline = parse_display_line(expanded, context().faces(), atoms);\n    }\n    catch (runtime_error& err)\n    {\n        write_to_debug_buffer(format(\"Error while parsing modelinefmt: {}\", err.what()));\n        modeline.push_back({ \"modelinefmt error, see *debug* buffer\", context().faces()[\"Error\"] });\n    }\n\n    return modeline;\n}\n\nvoid Client::change_buffer(Buffer& buffer, Optional<FunctionRef<void()>> set_selections)\n{\n    if (m_buffer_reload_dialog_opened)\n        close_buffer_reload_dialog();\n\n    if (context().buffer().flags() & Buffer::Flags::Locked)\n        throw runtime_error(\"Changing buffer is not allowed while current buffer is locked\");\n\n    buffer.flags() |= Buffer::Flags::Locked;\n    OnScopeEnd unlock{[&] { buffer.flags() &= ~Buffer::Flags::Locked; }};\n\n    auto& client_manager = ClientManager::instance();\n    WindowAndSelections ws = client_manager.get_free_window(buffer);\n\n    m_window->options().unregister_watcher(*this);\n    m_window->set_client(nullptr);\n    client_manager.add_free_window(std::move(m_window),\n                                   context().selections());\n\n    m_window = std::move(ws.window);\n    m_window->set_client(this);\n    m_window->options().register_watcher(*this);\n\n    if (set_selections)\n        (*set_selections)();\n    else\n    {\n        ScopedSelectionEdition selection_edition{context()};\n        context().selections_write_only() = std::move(ws.selections);\n    }\n\n    context().set_window(*m_window);\n\n    m_window->set_dimensions(m_ui->dimensions());\n    m_ui->set_ui_options(m_window->options()[\"ui_options\"].get<UserInterface::Options>());\n\n    m_window->hooks().run_hook(Hook::WinDisplay, buffer.name(), context());\n    force_redraw(true);\n}\n\nstatic bool is_inline(InfoStyle style)\n{\n    return style == InfoStyle::Inline or\n           style == InfoStyle::InlineAbove or\n           style == InfoStyle::InlineBelow;\n}\n\nvoid Client::redraw_ifn()\n{\n    Window& window = context().window();\n    if (window.needs_redraw(context()))\n        m_ui_pending |= Draw;\n\n    const auto& faces = context().faces();\n\n    if (m_ui_pending & Draw)\n    {\n        auto& display_buffer = window.update_display_buffer(context());\n        auto cursor_pos = window.display_coord(context().selections().main().cursor()).value_or(DisplayCoord{});\n        m_ui->draw(display_buffer, cursor_pos, faces[\"Default\"], faces[\"BufferPadding\"],\n                   window.last_display_setup().widget_columns);\n    }\n\n    const bool update_menu_anchor = (m_ui_pending & Draw) and not (m_ui_pending & MenuHide) and\n                                    not m_menu.items.empty() and m_menu.style == MenuStyle::Inline;\n    if ((m_ui_pending & MenuShow) or update_menu_anchor)\n    {\n        auto anchor = m_menu.style == MenuStyle::Inline ?\n            window.display_coord(m_menu.anchor) : DisplayCoord{};\n        if (not (m_ui_pending & MenuShow) and m_menu.ui_anchor != anchor)\n            m_ui_pending |= anchor ? (MenuShow | MenuSelect) : MenuHide;\n        m_menu.ui_anchor = anchor;\n    }\n\n    if (m_ui_pending & MenuShow and m_menu.ui_anchor)\n        m_ui->menu_show(m_menu.items, *m_menu.ui_anchor,\n                        faces[\"MenuForeground\"], faces[\"MenuBackground\"],\n                        m_menu.style);\n    if (m_ui_pending & MenuSelect and m_menu.ui_anchor)\n        m_ui->menu_select(m_menu.selected);\n    if (m_ui_pending & MenuHide)\n        m_ui->menu_hide();\n\n    const bool update_info_anchor = (m_ui_pending & Draw) and not (m_ui_pending & InfoHide) and\n                                    not m_info.content.empty() and is_inline(m_info.style);\n    if ((m_ui_pending & InfoShow) or update_info_anchor)\n    {\n        auto anchor = is_inline(m_info.style) ?\n             window.display_coord(m_info.anchor) : DisplayCoord{};\n        if (not (m_ui_pending & MenuShow) and m_info.ui_anchor != anchor)\n            m_ui_pending |= anchor ? InfoShow : InfoHide;\n        m_info.ui_anchor = anchor;\n    }\n\n    if (m_ui_pending & InfoShow and m_info.ui_anchor)\n        m_ui->info_show(m_info.title, m_info.content, *m_info.ui_anchor,\n                        faces[(is_inline(m_info.style) || m_info.style == InfoStyle::MenuDoc)\n                        ? \"InlineInformation\" : \"Information\"], m_info.style);\n    if (m_ui_pending & InfoHide)\n        m_ui->info_hide();\n\n    // This needs to be done *after* update_display_buffer as ithe mode line may rely on it to\n    // compute whether selections are visible.\n    DisplayLine mode_line = generate_mode_line();\n    if (mode_line.atoms() != m_mode_line.atoms())\n    {\n        m_ui_pending |= StatusLine;\n        m_mode_line = std::move(mode_line);\n    }\n    if (m_ui_pending & StatusLine)\n        m_ui->draw_status(m_status_prompt, m_status_content, m_status_cursor_pos, m_mode_line, faces[\"StatusLine\"]);\n\n    if (m_ui_pending != 0)\n        m_ui->refresh(m_ui_pending & Refresh);\n\n    m_ui_pending = 0;\n}\n\nvoid Client::force_redraw(bool full)\n{\n    if (full)\n        m_ui_pending |= Refresh | Draw | StatusLine |\n            (m_menu.items.empty() ? MenuHide : MenuShow | MenuSelect) |\n            (m_info.content.empty() ? InfoHide : InfoShow);\n    else\n        m_ui_pending |= Draw;\n}\n\nvoid Client::reload_buffer()\n{\n    Buffer& buffer = context().buffer();\n    try\n    {\n        reload_file_buffer(buffer);\n        context().print_status({ format(\"'{}' reloaded\", buffer.display_name()),\n                                 context().faces()[\"Information\"] });\n\n        m_window->hooks().run_hook(Hook::BufReload, buffer.name(), context());\n    }\n    catch (runtime_error& error)\n    {\n        context().print_status({ format(\"error while reloading buffer: '{}'\", error.what()),\n                                 context().faces()[\"Error\"] });\n        buffer.set_fs_status(get_fs_status(buffer.filename()));\n    }\n}\n\nvoid Client::on_buffer_reload_key(Key key)\n{\n    auto& buffer = context().buffer();\n\n    auto set_autoreload = [this](Autoreload autoreload) {\n        auto* option = &context().options()[\"autoreload\"];\n        // Do not touch global autoreload, set it at least at buffer level\n        if (&option->manager() == &GlobalScope::instance().options())\n            option = &context().buffer().options().get_local_option(\"autoreload\");\n        option->set(autoreload);\n    };\n\n    if (key == 'y' or key == 'Y' or key == Key::Return)\n    {\n        reload_buffer();\n        if (key == 'Y')\n            set_autoreload(Autoreload::Yes);\n    }\n    else if (key == 'n' or key == 'N' or key == Key::Escape)\n    {\n        // reread timestamp in case the file was modified again\n        buffer.set_fs_status(get_fs_status(buffer.filename()));\n        print_status({}, { format(\"'{}' kept\", buffer.display_name()),\n                           context().faces()[\"Information\"] }, -1);\n        if (key == 'N')\n            set_autoreload(Autoreload::No);\n    }\n    else\n    {\n        print_status({}, { format(\"'{}' is not a valid choice\", key),\n                           context().faces()[\"Error\"] }, -1);\n        m_input_handler.on_next_key(\"buffer-reload\", KeymapMode::None, [this](Key key, Context&){ on_buffer_reload_key(key); });\n        return;\n    }\n\n    for (auto& client : ClientManager::instance())\n    {\n        if (&client->context().buffer() == &buffer and\n            client->m_buffer_reload_dialog_opened)\n            client->close_buffer_reload_dialog();\n    }\n}\n\nvoid Client::close_buffer_reload_dialog()\n{\n    kak_assert(m_buffer_reload_dialog_opened);\n    // Reset first as this might check for reloading.\n    m_input_handler.reset_normal_mode();\n    m_buffer_reload_dialog_opened = false;\n    info_hide(true);\n}\n\nvoid Client::check_if_buffer_needs_reloading()\n{\n    if (m_buffer_reload_dialog_opened)\n        return;\n\n    Buffer& buffer = context().buffer();\n    auto reload = context().options()[\"autoreload\"].get<Autoreload>();\n    if (not (buffer.flags() & Buffer::Flags::File) or reload == Autoreload::No)\n        return;\n\n    try\n    {\n        const String& filename = buffer.filename();\n        const timespec ts = get_fs_timestamp(filename);\n        const auto status = buffer.fs_status();\n\n        if (ts == InvalidTime or ts == status.timestamp)\n            return;\n\n        if (MappedFile fd{filename};\n            fd.st.st_size == status.file_size and murmur3(fd.data, fd.st.st_size) == status.hash)\n            return;\n\n        if (reload == Autoreload::Ask)\n        {\n            info_show(format(\"reload '{}' ?\", buffer.display_name()),\n                      format(\"'{}' was modified externally\\n\"\n                             \" y, <ret>: reload | n, <esc>: keep\\n\"\n                             \" Y: always reload | N: always keep\\n\",\n                             buffer.display_name()), {}, InfoStyle::Modal);\n\n            m_buffer_reload_dialog_opened = true;\n            m_input_handler.on_next_key(\"buffer-reload\", KeymapMode::None, [this](Key key, Context&){ on_buffer_reload_key(key); });\n        }\n        else\n            reload_buffer();\n    }\n    catch (Kakoune::runtime_error& error)\n    {\n        write_to_debug_buffer(format(\"Error while checking if buffer {} changed: {}\", buffer.filename(), error.what()));\n    }\n}\n\nStringView Client::get_env_var(StringView name) const\n{\n    auto it = m_env_vars.find(name);\n    if (it == m_env_vars.end())\n        return {};\n    return it->value;\n}\n\nvoid Client::on_option_changed(const Option& option)\n{\n    if (option.name() == \"ui_options\")\n        m_ui->set_ui_options(option.get<UserInterface::Options>());\n    m_ui_pending |= Draw; // a highlighter might depend on the option, so we need to redraw\n}\n\nvoid Client::menu_show(Vector<DisplayLine> choices, BufferCoord anchor, MenuStyle style)\n{\n    m_menu = Menu{ std::move(choices), anchor, {}, style, -1 };\n    m_ui_pending |= MenuShow;\n    m_ui_pending &= ~MenuHide;\n}\n\nvoid Client::menu_select(int selected)\n{\n    m_menu.selected = selected;\n    m_ui_pending |= MenuSelect;\n    m_ui_pending &= ~MenuHide;\n}\n\nvoid Client::menu_hide()\n{\n    m_menu = Menu{};\n    m_ui_pending |= MenuHide;\n    m_ui_pending &= ~(MenuShow | MenuSelect);\n}\n\nvoid Client::info_show(DisplayLine title, DisplayLineList content, BufferCoord anchor, InfoStyle style)\n{\n    if (m_info.style == InfoStyle::Modal) // We already have a modal info opened, do not touch it.\n        return;\n\n    m_info = Info{ std::move(title), std::move(content), anchor, {}, style };\n    m_ui_pending |= InfoShow;\n    m_ui_pending &= ~InfoHide;\n    m_pending_clear &= ~PendingClear::Info;\n}\n\nvoid Client::info_show(StringView title, StringView content, BufferCoord anchor, InfoStyle style)\n{\n    if (not content.empty() and content.back() == '\\n')\n        content = content.substr(0, content.length() - 1);\n    info_show(title.empty() ? DisplayLine{} : DisplayLine{title.str(), Face{}},\n              content | split<StringView>('\\n')\n                      | transform([](StringView s) { return DisplayLine{replace(s, '\\t', ' '), Face{}}; })\n                      | gather<DisplayLineList>(),\n              anchor, style);\n}\n\nvoid Client::info_hide(bool even_modal)\n{\n    if (not even_modal and m_info.style == InfoStyle::Modal)\n        return;\n\n    m_info = Info{};\n    m_ui_pending |= InfoHide;\n    m_ui_pending &= ~InfoShow;\n}\n\nvoid Client::schedule_clear()\n{\n    if (not (m_ui_pending & InfoShow))\n        m_pending_clear |= PendingClear::Info;\n    if (not (m_ui_pending & StatusLine))\n        m_pending_clear |= PendingClear::StatusLine;\n}\n\nvoid Client::clear_pending()\n{\n    if (m_pending_clear & PendingClear::StatusLine)\n        print_status({}, {}, -1);\n    if (m_pending_clear & PendingClear::Info)\n        info_hide();\n    m_pending_clear = PendingClear::None;\n}\n\nconstexpr std::chrono::seconds wait_timeout{1};\n\nBusyIndicator::BusyIndicator(const Context& context,\n                             Function<DisplayLine(std::chrono::seconds)> status_message,\n                             TimePoint wait_time)\n    : m_context(context),\n      m_timer{wait_time + wait_timeout,\n        [this, status_message = std::move(status_message), wait_time](Timer& timer) {\n            if (not m_context.has_client())\n                return;\n            using namespace std::chrono;\n            const auto now = Clock::now();\n            timer.set_next_date(now + wait_timeout);\n\n            auto& client = m_context.client();\n            if (not m_previous_status)\n                m_previous_status.emplace(client.m_status_prompt, client.m_status_content, client.m_status_cursor_pos);\n\n            client.print_status({}, status_message(duration_cast<seconds>(now - wait_time)), -1);\n            client.redraw_ifn();\n        }, EventMode::Urgent} {}\n\nBusyIndicator::~BusyIndicator()\n{\n    if (m_previous_status and std::uncaught_exceptions() == 0) // restore the status line\n    {\n        auto& [prompt, content, cursor_pos] = *m_previous_status;\n        m_context.print_status(std::move(prompt), std::move(content), std::move(cursor_pos));\n        m_context.client().redraw_ifn();\n    }\n}\n\n}\n"
  },
  {
    "path": "src/client.hh",
    "content": "#ifndef client_hh_INCLUDED\n#define client_hh_INCLUDED\n\n#include \"array.hh\"\n#include \"clock.hh\"\n#include \"display_buffer.hh\"\n#include \"env_vars.hh\"\n#include \"event_manager.hh\"\n#include \"input_handler.hh\"\n#include \"safe_ptr.hh\"\n#include \"unique_ptr.hh\"\n#include \"utils.hh\"\n#include \"option.hh\"\n#include \"enum.hh\"\n\nnamespace Kakoune\n{\n\nclass Window;\nclass UserInterface;\nclass String;\nstruct Key;\n\nenum class InfoStyle;\nenum class MenuStyle;\n\nclass Client final : public SafeCountable, public OptionWatcher\n{\npublic:\n    using OnExitCallback = Function<void (int status)>;\n\n    Client(UniquePtr<UserInterface>&& ui,\n           UniquePtr<Window>&& window,\n           SelectionList selections,\n           int pid, EnvVarMap env_vars,\n           String name,\n           OnExitCallback on_exit);\n    ~Client();\n\n    Client(Client&&) = delete;\n\n    bool is_ui_ok() const;\n\n    bool process_pending_inputs();\n    bool has_pending_inputs() const { return not m_pending_keys.empty(); }\n\n    void menu_show(Vector<DisplayLine> choices, BufferCoord anchor, MenuStyle style);\n    void menu_select(int selected);\n    void menu_hide();\n\n    void info_show(DisplayLine title, DisplayLineList content, BufferCoord anchor, InfoStyle style);\n    void info_show(StringView title, StringView content, BufferCoord anchor, InfoStyle style);\n    void info_hide(bool even_modal = false);\n    bool info_pending() const { return m_ui_pending & PendingUI::InfoShow; };\n    bool status_line_pending() const { return m_ui_pending & PendingUI::StatusLine; };\n\n    void print_status(DisplayLine prompt, DisplayLine content, ColumnCount cursor_pos);\n\n    DisplayCoord dimensions() const;\n\n    void schedule_clear();\n    void clear_pending();\n\n    void force_redraw(bool full = false);\n    void redraw_ifn();\n\n    void check_if_buffer_needs_reloading();\n\n    Context& context() { return m_input_handler.context(); }\n    const Context& context() const { return m_input_handler.context(); }\n\n    InputHandler& input_handler() { return m_input_handler; }\n    const InputHandler& input_handler() const { return m_input_handler; }\n\n    void change_buffer(Buffer& buffer, Optional<FunctionRef<void()>> set_selection);\n\n    StringView get_env_var(StringView name) const;\n\n    void exit(int status) { m_on_exit(status); }\n\n    int pid() const { return m_pid; }\n\nprivate:\n    friend class BusyIndicator;\n\n    void on_option_changed(const Option& option) override;\n\n    void on_buffer_reload_key(Key key);\n    void close_buffer_reload_dialog();\n    void reload_buffer();\n\n    DisplayLine generate_mode_line() const;\n\n    UniquePtr<UserInterface> m_ui;\n    UniquePtr<Window> m_window;\n\n    const int m_pid;\n\n    OnExitCallback m_on_exit;\n\n    EnvVarMap m_env_vars;\n\n    InputHandler m_input_handler;\n\n    DisplayLine m_status_prompt;\n    DisplayLine m_status_content;\n    ColumnCount m_status_cursor_pos;\n    DisplayLine m_mode_line;\n\n    enum PendingUI : int\n    {\n        MenuShow   = 1 << 0,\n        MenuSelect = 1 << 1,\n        MenuHide   = 1 << 2,\n        InfoShow   = 1 << 3,\n        InfoHide   = 1 << 4,\n        StatusLine = 1 << 5,\n        Draw       = 1 << 6,\n        Refresh    = 1 << 7,\n    };\n    int m_ui_pending = 0;\n\n    enum class PendingClear\n    {\n        None = 0,\n        Info = 0b01,\n        StatusLine = 0b10\n    };\n    friend constexpr bool with_bit_ops(Meta::Type<PendingClear>) { return true; }\n    PendingClear m_pending_clear = PendingClear::None;\n\n\n    struct Menu\n    {\n        Vector<DisplayLine> items;\n        BufferCoord anchor;\n        Optional<DisplayCoord> ui_anchor;\n        MenuStyle style;\n        int selected;\n    } m_menu{};\n\n    struct Info\n    {\n        DisplayLine title;\n        DisplayLineList content;\n        BufferCoord anchor;\n        Optional<DisplayCoord> ui_anchor;\n        InfoStyle style;\n    } m_info{};\n\n    Vector<Key, MemoryDomain::Client> m_pending_keys;\n\n    bool m_buffer_reload_dialog_opened = false;\n};\n\nenum class Autoreload\n{\n    Yes,\n    No,\n    Ask\n};\n\nconstexpr auto enum_desc(Meta::Type<Autoreload>)\n{\n    return make_array<EnumDesc<Autoreload>>({\n        { Autoreload::Yes, \"yes\" },\n        { Autoreload::No, \"no\" },\n        { Autoreload::Ask, \"ask\" },\n        { Autoreload::Yes, \"true\" },\n        { Autoreload::No, \"false\" }\n    });\n}\n\nclass BusyIndicator\n{\npublic:\n    BusyIndicator(const Context& context,\n                  Function<DisplayLine(std::chrono::seconds)> status_message,\n                  TimePoint wait_time = Clock::now());\n    ~BusyIndicator();\nprivate:\n    const Context& m_context;\n    Timer m_timer;\n    struct PreviousStatus\n    {\n        DisplayLine prompt;\n        DisplayLine content;\n        ColumnCount cursor_pos;\n    };\n    Optional<PreviousStatus> m_previous_status;\n};\n\n}\n\n#endif // client_hh_INCLUDED\n"
  },
  {
    "path": "src/client_manager.cc",
    "content": "#include \"client_manager.hh\"\n\n#include \"buffer_manager.hh\"\n#include \"command_manager.hh\"\n#include \"hook_manager.hh\"\n#include \"face_registry.hh\"\n#include \"file.hh\"\n#include \"ranges.hh\"\n#include \"window.hh\"\n\nnamespace Kakoune\n{\n\nClientManager::ClientManager() = default;\nClientManager::~ClientManager()\n{\n    clear(true);\n}\n\nvoid ClientManager::clear(bool disconnect_clients)\n{\n    if (disconnect_clients)\n    {\n        while (not m_clients.empty())\n            remove_client(*m_clients.front(), true, 0);\n    }\n    else\n        m_clients.clear();\n    m_client_trash.clear();\n\n    for (auto& window : m_free_windows)\n        window.window->run_hook_in_own_context(Hook::WinClose,\n                                               window.window->buffer().name());\n    m_free_windows.clear();\n    m_window_trash.clear();\n}\n\nString ClientManager::generate_name() const\n{\n    for (int i = 0; true; ++i)\n    {\n        String name = format(\"client{}\", i);\n        if (not client_name_exists(name))\n            return name;\n    }\n}\n\nClient* ClientManager::create_client(UniquePtr<UserInterface>&& ui, int pid,\n                                     String name, EnvVarMap env_vars, StringView init_cmds,\n                                     StringView init_buffer, Optional<BufferCoord> init_coord,\n                                     Client::OnExitCallback on_exit)\n{\n    Buffer* buffer = nullptr;\n    if (not init_buffer.empty())\n        buffer = BufferManager::instance().get_buffer_ifp(init_buffer);\n    if (buffer == nullptr)\n        buffer = &BufferManager::instance().get_first_buffer();\n\n    buffer->flags() |= Buffer::Flags::Locked;\n    OnScopeEnd unlock{[&] { buffer->flags() &= ~Buffer::Flags::Locked; }};\n\n    WindowAndSelections ws = get_free_window(*buffer);\n    Client* client = new Client{std::move(ui), std::move(ws.window),\n                                std::move(ws.selections), pid,\n                                std::move(env_vars),\n                                name.empty() ? generate_name() : std::move(name),\n                                std::move(on_exit)};\n    m_clients.emplace_back(client);\n\n    auto& context = client->context();\n    if (context.buffer().name() == \"*scratch*\")\n        context.print_status({\"This *scratch* buffer won't be automatically saved\",\n                              context.faces()[\"Information\"]});\n\n    if (init_coord)\n    {\n        context.selections_write_only() = SelectionList(*buffer, context.buffer().clamp(*init_coord));\n        context.window().center_line(init_coord->line);\n    }\n\n    unlock.trigger();\n\n    try\n    {\n        context.hooks().run_hook(Hook::ClientCreate, context.name(), context);\n        CommandManager::instance().execute(init_cmds, context);\n    }\n    catch (Kakoune::runtime_error& error)\n    {\n        context.print_status({error.what().str(), context.faces()[\"Error\"]});\n        context.hooks().run_hook(Hook::RuntimeError, error.what(), context);\n    }\n\n    // Do not return the client if it already got moved to the trash\n    return contains(m_clients, client) ? client : nullptr;\n}\n\nbool ClientManager::process_pending_inputs()\n{\n    bool processed_some_input = false;\n    while (true)\n    {\n        bool had_input = false;\n        // Use index based iteration as a m_clients might get mutated during\n        // client input processing, which would break iterator based iteration.\n        // (its fine to skip a client if that happens as had_input will be true\n        // if a client triggers client removal)\n        for (int i = 0; i < m_clients.size(); )\n        {\n            if (not m_clients[i]->is_ui_ok())\n            {\n                remove_client(*m_clients[i], false, -1);\n                continue;\n            }\n            had_input = m_clients[i]->process_pending_inputs() or had_input;\n            processed_some_input |= had_input;\n            ++i;\n        }\n\n        if (not had_input)\n            break;\n    }\n    return processed_some_input;\n}\n\nbool ClientManager::has_pending_inputs() const\n{\n    return any_of(m_clients, [](auto&& c) { return c->has_pending_inputs(); });\n}\n\nvoid ClientManager::remove_client(Client& client, bool graceful, int status)\n{\n    auto it = find(m_clients, &client);\n    if (it == m_clients.end())\n    {\n        kak_assert(contains(m_client_trash, &client));\n        return;\n    }\n\n    m_client_trash.push_back(std::move(*it));\n    m_clients.erase(it);\n\n    auto& context = client.context();\n    context.hooks().run_hook(Hook::ClientClose, context.name(), context);\n\n    client.exit(status);\n\n    if (not graceful and m_clients.empty())\n        BufferManager::instance().backup_modified_buffers();\n}\n\nWindowAndSelections ClientManager::get_free_window(Buffer& buffer)\n{\n    kak_assert(contains(BufferManager::instance(), &buffer));\n    auto it = find_if(m_free_windows | reverse(),\n                      [&](const WindowAndSelections& ws)\n                      { return &ws.window->buffer() == &buffer; });\n\n    if (it == m_free_windows.rend())\n        return { make_unique_ptr<Window>(buffer), { buffer, Selection{} } };\n\n    WindowAndSelections res = std::move(*it);\n    m_free_windows.erase(it.base()-1);\n    res.selections.update();\n    return res;\n}\n\nvoid ClientManager::add_free_window(UniquePtr<Window>&& window, SelectionList selections)\n{\n    if (not contains(BufferManager::instance(), &window->buffer()))\n    {\n        m_window_trash.push_back(std::move(window));\n        return;\n    }\n\n    window->clear_display_buffer();\n    m_free_windows.push_back({std::move(window), std::move(selections)});\n}\n\nvoid ClientManager::ensure_no_client_uses_buffer(Buffer& buffer)\n{\n    for (auto& client : m_clients)\n        client->context().forget_buffer(buffer);\n\n    Vector<UniquePtr<Window>> removed_windows;\n    m_free_windows.erase(remove_if(m_free_windows,\n                                   [&buffer, &removed_windows](WindowAndSelections& ws) {\n                                       if (&ws.window->buffer() != &buffer)\n                                           return false;\n                                       removed_windows.push_back(std::move(ws.window));\n                                       return true;\n                                   }),\n                         m_free_windows.end());\n\n    for (auto&& removed_window : removed_windows)\n    {\n        removed_window->run_hook_in_own_context(Hook::WinClose,\n                                                removed_window->buffer().name());\n        m_window_trash.push_back(std::move(removed_window));\n    }\n}\n\nvoid ClientManager::clear_window_trash()\n{\n    m_window_trash.clear();\n}\n\nvoid ClientManager::clear_client_trash()\n{\n    m_client_trash.clear();\n}\n\nbool ClientManager::client_name_exists(StringView name) const\n{\n    return const_cast<ClientManager*>(this)->get_client_ifp(name) != nullptr;\n}\n\nClient* ClientManager::get_client_ifp(StringView name)\n{\n    for (auto& client : m_clients)\n    {\n        if (client->context().name() == name)\n            return client.get();\n    }\n    return nullptr;\n}\n\nClient& ClientManager::get_client(StringView name)\n{\n    if (Client* client = get_client_ifp(name))\n        return *client;\n    throw runtime_error(format(\"no such client: '{}'\", name));\n}\n\nvoid ClientManager::redraw_clients() const\n{\n    for (auto& client : m_clients)\n        client->redraw_ifn();\n}\n\nCandidateList ClientManager::complete_client_name(StringView prefix,\n                                                  ByteCount cursor_pos) const\n{\n    auto c = m_clients | transform([](const UniquePtr<Client>& c) -> const String&\n                                   { return c->context().name(); });\n    return complete(prefix, cursor_pos, c);\n}\n\n}\n"
  },
  {
    "path": "src/client_manager.hh",
    "content": "#ifndef client_manager_hh_INCLUDED\n#define client_manager_hh_INCLUDED\n\n#include \"client.hh\"\n#include \"completion.hh\"\n\nnamespace Kakoune\n{\n\nstruct WindowAndSelections\n{\n    UniquePtr<Window> window;\n    SelectionList selections;\n};\n\nclass ClientManager : public Singleton<ClientManager>\n{\npublic:\n    ClientManager();\n    ~ClientManager();\n\n    Client* create_client(UniquePtr<UserInterface>&& ui, int pid,\n                          String name, EnvVarMap env_vars, StringView init_cmds,\n                          StringView init_buffer, Optional<BufferCoord> init_coord,\n                          Client::OnExitCallback on_exit);\n\n    bool   empty() const { return m_clients.empty(); }\n    size_t count() const { return m_clients.size(); }\n\n    void clear(bool exit);\n\n    void ensure_no_client_uses_buffer(Buffer& buffer);\n\n    WindowAndSelections get_free_window(Buffer& buffer);\n    void add_free_window(UniquePtr<Window>&& window, SelectionList selections);\n\n    void redraw_clients() const;\n    bool process_pending_inputs();\n    bool has_pending_inputs() const;\n\n    Client*  get_client_ifp(StringView name);\n    Client&  get_client(StringView name);\n    bool client_name_exists(StringView name) const;\n    void remove_client(Client& client, bool graceful, int status);\n\n    using ClientList = Vector<UniquePtr<Client>, MemoryDomain::Client>;\n    using iterator = ClientList::const_iterator;\n\n    iterator begin() const { return m_clients.begin(); }\n    iterator end() const { return m_clients.end(); }\n\n    CandidateList complete_client_name(StringView name,\n                                       ByteCount cursor_pos = -1) const;\n\n    void clear_window_trash();\n    void clear_client_trash();\nprivate:\n    String generate_name() const;\n\n    ClientList m_clients;\n    ClientList m_client_trash;\n    Vector<WindowAndSelections, MemoryDomain::Client> m_free_windows;\n    Vector<UniquePtr<Window>, MemoryDomain::Client> m_window_trash;\n};\n\n}\n\n#endif // client_manager_hh_INCLUDED\n"
  },
  {
    "path": "src/clock.hh",
    "content": "#ifndef clock_hh_INCLUDED\n#define clock_hh_INCLUDED\n\n#include <chrono>\n\nnamespace Kakoune\n{\n\nusing Clock = std::chrono::steady_clock;\nusing TimePoint = Clock::time_point;\n\n}\n\n#endif // clock_hh_INCLUDED\n"
  },
  {
    "path": "src/color.cc",
    "content": "#include \"color.hh\"\n\n#include \"exception.hh\"\n#include \"ranges.hh\"\n#include \"format.hh\"\n\n#include <cstdio>\n\nnamespace Kakoune\n{\n\nstatic constexpr const char* color_names[] = {\n    \"default\",\n    \"black\",\n    \"red\",\n    \"green\",\n    \"yellow\",\n    \"blue\",\n    \"magenta\",\n    \"cyan\",\n    \"white\",\n    \"bright-black\",\n    \"bright-red\",\n    \"bright-green\",\n    \"bright-yellow\",\n    \"bright-blue\",\n    \"bright-magenta\",\n    \"bright-cyan\",\n    \"bright-white\",\n};\n\nbool is_color_name(StringView color)\n{\n    return contains(color_names, color);\n}\n\nColor str_to_color(StringView color)\n{\n    auto it = find_if(color_names, [&](const char* c){ return color == c; });\n    if (it != std::end(color_names))\n        return static_cast<Color::NamedColor>(it - color_names);\n\n    auto hval = [&color](char c) -> int\n    {\n        if (c >= 'A' and c <= 'F')\n            return 10 + c - 'A';\n        else if (c >= 'a' and c <= 'f')\n            return 10 + c - 'a';\n        else if (c >= '0' and c <= '9')\n            return c - '0';\n        throw runtime_error(format(\"invalid digit '{}' in '{}'\", c, color));\n    };\n\n    if (color.length() == 10 and color.substr(0_byte, 4_byte) == \"rgb:\")\n        return { (unsigned char)(hval(color[4]) * 16 + hval(color[5])),\n                 (unsigned char)(hval(color[6]) * 16 + hval(color[7])),\n                 (unsigned char)(hval(color[8]) * 16 + hval(color[9])) };\n    if (color.length() == 13 and color.substr(0_byte, 5_byte) == \"rgba:\")\n        return { (unsigned char)(hval(color[5])  * 16 + hval(color[6])),\n                 (unsigned char)(hval(color[7])  * 16 + hval(color[8])),\n                 (unsigned char)(hval(color[9])  * 16 + hval(color[10])),\n                 (unsigned char)(hval(color[11]) * 16 + hval(color[12])) };\n\n    throw runtime_error(format(\"unable to parse color: '{}'\", color));\n    return Color::Default;\n}\n\nString to_string(Color color)\n{\n    if (color.isRGB())\n    {\n        char buffer[14];\n        if (color.a == 255)\n            format_to(buffer, \"rgb:{:02}{:02}{:02}\", hex(color.r), hex(color.g), hex(color.b));\n        else\n            format_to(buffer, \"rgba:{:02}{:02}{:02}{:02}\", hex(color.r), hex(color.g), hex(color.b), hex(color.a));\n        return buffer;\n    }\n    else\n    {\n        size_t index = static_cast<size_t>(color.color);\n        kak_assert(index < std::end(color_names) - std::begin(color_names));\n        return color_names[index];\n    }\n}\n\nString option_to_string(Color color)\n{\n    return to_string(color);\n}\n\nColor option_from_string(Meta::Type<Color>, StringView str)\n{\n    return str_to_color(str);\n}\n\n}\n"
  },
  {
    "path": "src/color.hh",
    "content": "#ifndef color_hh_INCLUDED\n#define color_hh_INCLUDED\n\n#include \"exception.hh\"\n#include \"hash.hh\"\n#include \"meta.hh\"\n#include \"assert.hh\"\n\nnamespace Kakoune\n{\n\nclass String;\nclass StringView;\n\nstruct Color\n{\n    enum NamedColor : unsigned char\n    {\n        Default,\n        Black,\n        Red,\n        Green,\n        Yellow,\n        Blue,\n        Magenta,\n        Cyan,\n        White,\n        BrightBlack,\n        BrightRed,\n        BrightGreen,\n        BrightYellow,\n        BrightBlue,\n        BrightMagenta,\n        BrightCyan,\n        BrightWhite,\n        RGB,\n    };\n\n    union\n    {\n        NamedColor color;\n        unsigned char a;\n    };\n    unsigned char r = 0;\n    unsigned char g = 0;\n    unsigned char b = 0;\n\n    constexpr bool isRGB() const { return a >= RGB; }\n\n    constexpr Color() : Color{Default} {}\n    constexpr Color(NamedColor c) : color{c} {}\n    constexpr Color(unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255)\n        : a{a}, r{r}, g{g}, b{b}\n    {\n        validate_alpha();\n    }\n\nprivate:\n    constexpr void validate_alpha() {\n        static_assert(RGB == 17);\n        if (a < RGB)\n            throw runtime_error(\"Colors alpha must be > 16\");\n    }\n};\n\nconstexpr bool operator==(Color lhs, Color rhs)\n{\n    return lhs.color == rhs.color and\n           lhs.r == rhs.r and lhs.g == rhs.g and lhs.b == rhs.b;\n}\n\nColor str_to_color(StringView color);\nString to_string(Color color);\n\nString option_to_string(Color color);\nColor option_from_string(Meta::Type<Color>, StringView str);\n\nbool is_color_name(StringView color);\n\nconstexpr size_t hash_value(const Color& val)\n{\n    return val.isRGB() ?\n        hash_values(val.a, val.r, val.g, val.b)\n      : hash_value(val.color);\n}\n\n}\n\n#endif // color_hh_INCLUDED\n"
  },
  {
    "path": "src/command_manager.cc",
    "content": "#include \"command_manager.hh\"\n\n#include \"alias_registry.hh\"\n#include \"assert.hh\"\n#include \"context.hh\"\n#include \"debug.hh\"\n#include \"flags.hh\"\n#include \"file.hh\"\n#include \"hook_manager.hh\"\n#include \"optional.hh\"\n#include \"option_manager.hh\"\n#include \"option_types.hh\"\n#include \"profile.hh\"\n#include \"ranges.hh\"\n#include \"regex.hh\"\n#include \"register_manager.hh\"\n#include \"shell_manager.hh\"\n#include \"scope.hh\"\n#include \"utils.hh\"\n#include \"unit_tests.hh\"\n\n#include <algorithm>\n\nnamespace Kakoune\n{\n\nbool CommandManager::command_defined(StringView command_name) const\n{\n    return m_commands.find(command_name) != m_commands.end();\n}\n\nvoid CommandManager::register_command(String command_name,\n                                      CommandFunc func,\n                                      String docstring,\n                                      ParameterDesc param_desc,\n                                      CommandFlags flags,\n                                      CommandHelper helper,\n                                      CommandCompleter completer)\n{\n    m_commands[command_name] = { std::move(func),\n                                 std::move(docstring),\n                                 std::move(param_desc),\n                                 flags,\n                                 std::move(helper),\n                                 std::move(completer) };\n}\n\nvoid CommandManager::set_command_completer(StringView command_name, CommandCompleter completer)\n{\n    auto it = m_commands.find(command_name);\n    if (it == m_commands.end())\n        throw runtime_error(format(\"no such command '{}'\", command_name));\n\n    it->value.completer = std::move(completer);\n}\n\nbool CommandManager::module_defined(StringView module_name) const\n{\n    return m_modules.find(module_name) != m_modules.end();\n}\n\nvoid CommandManager::register_module(String module_name, String commands)\n{\n    auto module = m_modules.find(module_name);\n    if (module != m_modules.end() and module->value.state != Module::State::Registered)\n        throw runtime_error{format(\"module already loaded: '{}'\", module_name)};\n\n    m_modules[module_name] = { Module::State::Registered, std::move(commands) };\n}\n\nvoid CommandManager::load_module(StringView module_name, Context& context)\n{\n    auto module = m_modules.find(module_name);\n    if (module == m_modules.end())\n        throw runtime_error{format(\"no such module: '{}'\", module_name)};\n    switch (module->value.state)\n    {\n        case Module::State::Loading:\n            throw runtime_error(format(\"module '{}' loaded recursively\", module_name));\n        case Module::State::Loaded: return;\n        case Module::State::Registered: default: break;\n    }\n\n    {\n        module->value.state = Module::State::Loading;\n        auto restore_state = OnScopeEnd([&] { module->value.state = Module::State::Registered; });\n\n        Context empty_context{Context::EmptyContextFlag{}};\n        execute(module->value.commands, empty_context);\n    }\n    module->value.commands.clear();\n    module->value.state = Module::State::Loaded;\n\n    context.hooks().run_hook(Hook::ModuleLoaded, module_name, context);\n}\n\nHashSet<String> CommandManager::loaded_modules() const\n{\n    return m_modules | filter([](auto&& elem) { return elem.value.state == Module::State::Loaded; })\n                     | transform([](auto&& elem) { return elem.key; })\n                     | gather<HashSet<String>>();\n}\n\nstruct parse_error : runtime_error\n{\n    parse_error(StringView error)\n        : runtime_error{format(\"parse error: {}\", error)} {}\n};\n\nnamespace\n{\n\nbool is_command_separator(char c)\n{\n    return c == ';' or c == '\\n';\n}\n\nstruct ParseResult\n{\n    String content;\n    bool terminated;\n};\n\ntemplate<typename Delimiter>\nParseResult parse_quoted(ParseState& state, Delimiter delimiter)\n{\n    static_assert(std::is_same_v<Delimiter, char> or std::is_same_v<Delimiter, Codepoint>);\n    auto read = [](const char*& it, const char* end) {\n        if constexpr (std::is_same_v<Delimiter, Codepoint>)\n            return utf8::read_codepoint(it, end);\n        else\n            return *it++;\n    };\n\n    const char* beg = state.pos;\n    const char* end = state.str.end();\n    String str;\n\n    while (state.pos != end)\n    {\n        const char* cur = state.pos;\n        const auto c = read(state.pos, end);\n        if (c == delimiter)\n        {\n            auto next = state.pos;\n            if (next == end || read(next, end) != delimiter)\n            {\n                if (str.empty())\n                    return {String{String::NoCopy{}, {beg, cur}}, true};\n\n                str += StringView{beg, cur};\n                return {str, true};\n            }\n            str += StringView{beg, state.pos};\n            state.pos = beg = next;\n        }\n    }\n    if (beg < end)\n        str += StringView{beg, end};\n    return {str, false};\n}\n\ntemplate<char opening_delimiter, char closing_delimiter>\nParseResult parse_quoted_balanced(ParseState& state)\n{\n    int level = 1;\n    const char* pos = state.pos;\n    const char* beg = pos;\n    const char* end = state.str.end();\n    while (pos != end)\n    {\n        const char c = *pos++;\n        if (c == opening_delimiter)\n            ++level;\n        else if (c == closing_delimiter and --level == 0)\n            break;\n    }\n    state.pos = pos;\n    const bool terminated = (level == 0);\n    return {String{String::NoCopy{}, {beg, pos - terminated}}, terminated};\n}\n\nbool is_ascii_horizontal_blank(char c)\n{\n    return c == '\\t'      or\n           c == '\\f'      or\n           c == ' ';\n}\n\nString parse_unquoted(ParseState& state)\n{\n    const char* beg = state.pos;\n    const char* end = state.str.end();\n\n    String str;\n\n    while (state.pos != end)\n    {\n        const char c = *state.pos;\n        if (is_command_separator(c) or is_ascii_horizontal_blank(c))\n        {\n            str += StringView{beg, state.pos};\n            if (state.pos != beg and *(state.pos - 1) == '\\\\')\n            {\n                str.back() = c;\n                beg = state.pos+1;\n            }\n            else\n                return str;\n        }\n        ++state.pos;\n    }\n    if (beg < end)\n        str += StringView{beg, end};\n    return str;\n}\n\nToken::Type token_type(StringView type_name, bool throw_on_invalid)\n{\n    if (type_name == \"\")\n        return Token::Type::RawQuoted;\n    else if (type_name == \"sh\")\n        return Token::Type::ShellExpand;\n    else if (type_name == \"reg\")\n        return Token::Type::RegisterExpand;\n    else if (type_name == \"opt\")\n        return Token::Type::OptionExpand;\n    else if (type_name == \"val\")\n        return Token::Type::ValExpand;\n    else if (type_name == \"arg\")\n        return Token::Type::ArgExpand;\n    else if (type_name == \"file\")\n        return Token::Type::FileExpand;\n    else if (type_name == \"exp\")\n        return Token::Type::Expand;\n    else if (throw_on_invalid)\n        throw parse_error{format(\"unknown expand '{}'\", type_name)};\n    else\n        return Token::Type::RawQuoted;\n}\n\nvoid skip_blanks_and_comments(ParseState& state)\n{\n    while (state)\n    {\n        const char c = *state.pos;\n        if (is_ascii_horizontal_blank(c))\n            ++state.pos;\n        else if (c == '\\\\' and state.pos + 1 != state.str.end() and\n                 state.pos[1] == '\\n')\n            state.pos += 2;\n        else if (c == '#')\n        {\n            while (state and *state.pos != '\\n')\n                ++state.pos;\n        }\n        else\n            break;\n    }\n}\n\nBufferCoord compute_coord(StringView s)\n{\n    BufferCoord coord{0,0};\n    for (auto c : s)\n    {\n        if (c == '\\n')\n        {\n            ++coord.line;\n            coord.column = 0;\n        }\n        else\n            ++coord.column;\n    }\n    return coord;\n}\n\nToken parse_percent_token(ParseState& state, bool throw_on_unterminated)\n{\n    kak_assert(state.pos[-1] == '%');\n    const auto type_start = state.pos;\n    while (state and *state.pos >= 'a' and *state.pos <= 'z')\n        ++state.pos;\n    StringView type_name{type_start, state.pos};\n\n    bool at_end = state.pos == state.str.end();\n    const Codepoint opening_delimiter = utf8::read_codepoint(state.pos, state.str.end());\n    if (at_end or iswalpha(opening_delimiter))\n    {\n        if (throw_on_unterminated)\n            throw parse_error{format(\"expected a string delimiter after '%{}'\",\n                                     type_name)};\n        return {};\n    }\n\n    Token::Type type = token_type(type_name, throw_on_unterminated);\n\n    constexpr struct CharPair { char opening; char closing; ParseResult (*parse_func)(ParseState&); } matching_pairs[] = {\n        { '(', ')', parse_quoted_balanced<'(', ')'> },\n        { '[', ']', parse_quoted_balanced<'[', ']'> },\n        { '{', '}', parse_quoted_balanced<'{', '}'> },\n        { '<', '>', parse_quoted_balanced<'<', '>'> }\n    };\n\n    auto start = state.pos;\n    const ByteCount byte_pos = start - state.str.begin();\n\n    if (auto it = find_if(matching_pairs, [=](const CharPair& cp) { return opening_delimiter == cp.opening; });\n        it != std::end(matching_pairs))\n    {\n        auto quoted = it->parse_func(state);\n        if (throw_on_unterminated and not quoted.terminated)\n        {\n            auto coord = compute_coord({state.str.begin(), start});\n            throw parse_error{format(\"{}:{}: unterminated string '%{}{}...{}'\",\n                                     coord.line+1, coord.column+1, type_name,\n                                     it->opening, it->closing)};\n        }\n\n        return {type, byte_pos, std::move(quoted.content), quoted.terminated};\n    }\n    else\n    {\n        const bool is_ascii = opening_delimiter < 128;\n        auto quoted = is_ascii ? parse_quoted(state, (char)opening_delimiter) : parse_quoted(state, opening_delimiter);\n\n        if (throw_on_unterminated and not quoted.terminated)\n        {\n            auto coord = compute_coord({state.str.begin(), start});\n            throw parse_error{format(\"{}:{}: unterminated string '%{}{}...{}'\",\n                                     coord.line+1, coord.column+1, type_name,\n                                     opening_delimiter, opening_delimiter)};\n        }\n\n        return {type, byte_pos, std::move(quoted.content), quoted.terminated};\n    }\n}\n\ntemplate<typename Target>\n    requires (std::is_same_v<Target, Vector<String>> or std::is_same_v<Target, String>)\nvoid expand_token(Token&& token, const Context& context, const ShellContext& shell_context, Target& target)\n{\n    constexpr bool single = std::is_same_v<Target, String>;\n    auto set_target = [&](auto&& s) {\n        if constexpr (single)\n            target = std::move(s);\n        else if constexpr (std::is_same_v<std::remove_cvref_t<decltype(s)>, String>)\n            target.push_back(std::move(s));\n        else if constexpr (std::is_same_v<decltype(s), Vector<String>&&>)\n            target.insert(target.end(), std::make_move_iterator(s.begin()), std::make_move_iterator(s.end()));\n        else\n            target.insert(target.end(), s.begin(), s.end());\n    };\n\n    auto&& content = token.content;\n    switch (token.type)\n    {\n    case Token::Type::ShellExpand:\n    {\n        auto str = ShellManager::instance().eval(\n            content, context, StringView{},\n            ShellManager::Flags::WaitForStdout,\n            shell_context).first;\n\n        if (not str.empty() and str.back() == '\\n')\n            str.resize(str.length() - 1, 0);\n\n        return set_target(std::move(str));\n    }\n    case Token::Type::RegisterExpand:\n        if constexpr (single)\n            return set_target(context.main_sel_register_value(content).str());\n        else\n            return set_target(RegisterManager::instance()[content].get(context));\n    case Token::Type::OptionExpand:\n    {\n        auto& opt = context.options()[content];\n        if constexpr (single)\n            return set_target(opt.get_as_string(Quoting::Raw));\n        else\n            return set_target(opt.get_as_strings());\n    }\n    case Token::Type::ValExpand:\n    {\n        auto it = shell_context.env_vars.find(content);\n        if (it != shell_context.env_vars.end())\n            return set_target(it->value);\n\n        auto val = ShellManager::instance().get_val(content, context);\n        if constexpr (single)\n            return set_target(join(val, ' ', false));\n        else\n            return set_target(std::move(val));\n    }\n    case Token::Type::ArgExpand:\n    {\n        auto& params = shell_context.params;\n        if (content == '@')\n        {\n            if constexpr (single)\n                return set_target(join(params, ' ', false));\n            else\n                return set_target(params);\n        }\n\n        const int arg = str_to_int(content);\n        if (arg < 1)\n            throw runtime_error(\"invalid argument index\");\n        return set_target(arg <= params.size() ? params[arg-1] : String{});\n    }\n    case Token::Type::FileExpand:\n        return set_target(read_file(content));\n    case Token::Type::Expand:\n        return set_target(expand(content, context, shell_context));\n    case Token::Type::Raw:\n    case Token::Type::RawQuoted:\n        return set_target(std::move(content));\n    default: kak_assert(false);\n    }\n}\n\n}\n\nCommandParser::CommandParser(StringView command_line) : m_state{command_line, command_line.begin()} {}\n\nOptional<Token> CommandParser::read_token(bool throw_on_unterminated)\n{\n    skip_blanks_and_comments(m_state);\n    if (not m_state)\n        return {};\n\n    const StringView line = m_state.str;\n    const char* start = m_state.pos;\n\n    const char c = *m_state.pos;\n    if (c == '\"' or c == '\\'')\n    {\n        start = ++m_state.pos;\n        ParseResult quoted = parse_quoted(m_state, c);\n        if (throw_on_unterminated and not quoted.terminated)\n            throw parse_error{format(\"unterminated string {0}...{0}\", c)};\n        return Token{c == '\"' ? Token::Type::Expand\n                              : Token::Type::RawQuoted,\n                     start - line.begin(), std::move(quoted.content),\n                     quoted.terminated};\n    }\n    else if (c == '%')\n    {\n        ++m_state.pos;\n        return parse_percent_token(m_state, throw_on_unterminated);\n    }\n    else if (is_command_separator(c))\n        return Token{Token::Type::CommandSeparator,\n                     ++m_state.pos - line.begin(), {}};\n    else\n    {\n        if (c == '\\\\' and m_state.pos + 1 != m_state.str.end())\n        {\n            const char next = m_state.pos[1];\n            if (next == '%' or next == '\\'' or next == '\"')\n                ++m_state.pos;\n        }\n        return Token{Token::Type::Raw, start - line.begin(), parse_unquoted(m_state)};\n    }\n    return {};\n}\n\ntemplate<typename Postprocess>\nString expand_impl(StringView str, const Context& context,\n                   const ShellContext& shell_context,\n                   Postprocess postprocess)\n{\n    ParseState state{str, str.begin()};\n    String res;\n    auto beg = state.pos;\n    while (state)\n    {\n        if (*state.pos++ == '%')\n        {\n            if (state and *state.pos == '%')\n            {\n                res += StringView{beg, state.pos};\n                beg = ++state.pos;\n            }\n            else\n            {\n                res += StringView{beg, state.pos-1};\n                String token;\n                expand_token(parse_percent_token(state, true), context, shell_context, token);\n                res += postprocess(token);\n                beg = state.pos;\n            }\n        }\n    }\n    res += StringView{beg, state.pos};\n    return res;\n}\n\nString expand(StringView str, const Context& context,\n              const ShellContext& shell_context)\n{\n    return expand_impl(str, context, shell_context, [](String s){ return s; });\n}\n\nString expand(StringView str, const Context& context,\n              const ShellContext& shell_context,\n              const FunctionRef<String (String)>& postprocess)\n{\n    return expand_impl(str, context, shell_context, postprocess);\n}\n\nStringView resolve_alias(const Context& context, StringView name)\n{\n    auto alias = context.aliases()[name];\n    return alias.empty() ? name : alias;\n}\n\nvoid CommandManager::execute_single_command(CommandParameters params,\n                                            Context& context,\n                                            const ShellContext& shell_context)\n{\n    if (params.empty())\n        return;\n\n    constexpr int max_command_depth = 100;\n    if (m_command_depth > max_command_depth)\n        throw runtime_error(\"maximum nested command depth hit\");\n\n    ++m_command_depth;\n    auto pop_depth = OnScopeEnd([this] { --m_command_depth; });\n\n    auto command_it = m_commands.find(resolve_alias(context, params[0]));\n    if (command_it == m_commands.end())\n        throw runtime_error(\"no such command\");\n\n    auto debug_flags = context.options()[\"debug\"].get<DebugFlags>();\n    if (debug_flags & DebugFlags::Commands)\n        write_to_debug_buffer(format(\"command {}\", join(params, ' ')));\n\n    ProfileScope profile{debug_flags, [&](std::chrono::microseconds duration) {\n        write_to_debug_buffer(format(\"command {} took {} us\", params[0], duration.count()));\n    }};\n\n    command_it->value.func({{params.begin()+1, params.end()}, command_it->value.param_desc},\n                           context, shell_context);\n}\n\nvoid CommandManager::execute(StringView command_line,\n                             Context& context, const ShellContext& shell_context)\n{\n    CommandParser parser(command_line);\n\n    ByteCount command_pos{};\n    Vector<String> params;\n    while (true)\n    {\n        Optional<Token> token = parser.read_token(true);\n        if (not token or token->type == Token::Type::CommandSeparator)\n        {\n            try\n            {\n                execute_single_command(params, context, shell_context);\n            }\n            catch (failure& error)\n            {\n                throw;\n            }\n            catch (runtime_error& error)\n            {\n                auto coord = compute_coord(command_line.substr(0_byte, command_pos));\n                error.set_what(format(\"{}:{}: '{}': {}\", coord.line+1, coord.column+1,\n                                      params[0], error.what()));\n                throw;\n            }\n\n            if (not token)\n                return;\n\n            params.clear();\n            continue;\n        }\n\n        if (params.empty())\n            command_pos = token->pos;\n\n        if (token->type == Token::Type::ArgExpand and token->content == '@')\n            params.insert(params.end(), shell_context.params.begin(),\n                          shell_context.params.end());\n        else\n            expand_token(*std::move(token), context, shell_context, params);\n    }\n}\n\nOptional<CommandInfo> CommandManager::command_info(const Context& context, StringView command_line) const\n{\n    CommandParser parser{command_line};\n    Vector<Token> tokens;\n    while (auto token = parser.read_token(false))\n    {\n        if (token->type == Token::Type::CommandSeparator)\n            tokens.clear();\n        else\n            tokens.push_back(std::move(*token));\n    }\n\n    if (tokens.empty() or\n        (tokens.front().type != Token::Type::Raw and\n         tokens.front().type != Token::Type::RawQuoted))\n        return {};\n\n    auto cmd = m_commands.find(resolve_alias(context, tokens.front().content));\n    if (cmd == m_commands.end())\n        return {};\n\n    CommandInfo res;\n    res.name = cmd->key;\n    if (not cmd->value.docstring.empty())\n        res.info += cmd->value.docstring + \"\\n\";\n\n    if (cmd->value.helper)\n    {\n        Vector<String> params;\n        for (auto it = tokens.begin() + 1; it != tokens.end(); ++it)\n        {\n            if (it->type == Token::Type::Raw or\n                it->type == Token::Type::RawQuoted or\n                it->type == Token::Type::Expand)\n                params.push_back(it->content);\n        }\n        String helpstr = cmd->value.helper(context, params);\n        if (not helpstr.empty())\n            res.info += format(\"{}\\n\", helpstr);\n    }\n\n    String aliases;\n    for (auto& alias : context.aliases().aliases_for(cmd->key))\n        aliases += \" \" + alias;\n    if (not aliases.empty())\n        res.info += format(\"Aliases:{}\\n\", aliases);\n\n    auto& switches = cmd->value.param_desc.switches;\n    if (not switches.empty())\n        res.info += format(\"Switches:\\n{}\", indent(generate_switches_doc(switches)));\n\n    return res;\n}\n\nCompletions CommandManager::complete_command_name(const Context& context, StringView query) const\n{\n    auto commands = m_commands\n            | filter([](const CommandMap::Item& cmd) { return not (cmd.value.flags & CommandFlags::Hidden); })\n            | transform(&CommandMap::Item::key);\n\n    auto aliases = context.aliases().flatten_aliases()\n            | transform(&HashItem<String, String>::key);\n\n    return {0, query.length(),\n            Kakoune::complete(query, query.length(), concatenated(commands, aliases)),\n            Completions::Flags::Menu | Completions::Flags::NoEmpty};\n}\n\nCompletions CommandManager::complete_module_name(StringView query) const\n{\n    return {0, query.length(),\n            Kakoune::complete(query, query.length(), m_modules | filter([](auto&& item) { return item.value.state == Module::State::Registered; })\n                                                               | transform(&ModuleMap::Item::key))};\n}\n\nstatic Completions complete_expansion(const Context& context,\n                                      Token token, ByteCount start,\n                                      ByteCount cursor_pos, ByteCount pos_in_token)\n{\n    switch (token.type) {\n    case Token::Type::RegisterExpand:\n        return { start, cursor_pos,\n                 RegisterManager::instance().complete_register_name(\n                     token.content, pos_in_token) };\n\n    case Token::Type::OptionExpand:\n        return { start, cursor_pos,\n                 GlobalScope::instance().option_registry().complete_option_name(\n                     token.content, pos_in_token) };\n\n    case Token::Type::ShellExpand:\n        return offset_pos(shell_complete(context, token.content,\n                                         pos_in_token), start);\n\n    case Token::Type::ValExpand:\n        return { start, cursor_pos,\n                 ShellManager::instance().complete_env_var(\n                     token.content, pos_in_token) };\n\n    case Token::Type::FileExpand:\n    {\n        const auto& ignored_files = context.options()[\"ignored_files\"].get<Regex>();\n        return { start, cursor_pos, complete_filename(\n                 token.content, ignored_files, pos_in_token, FilenameFlags::Expand) };\n    }\n\n    default:\n        kak_assert(false);\n        throw runtime_error(\"unknown expansion\");\n    }\n}\n\nstatic Completions complete_expand(const Context& context,\n                                        StringView prefix, ByteCount start,\n                                        ByteCount cursor_pos, ByteCount pos_in_token)\n{\n    ParseState state{prefix, prefix.begin()};\n    while (state)\n    {\n        if (*state.pos++ == '%')\n        {\n            if (state and *state.pos == '%')\n                ++state.pos;\n            else\n            {\n                auto token = parse_percent_token(state, false);\n                if (token.terminated)\n                    continue;\n                if (token.type == Token::Type::Raw or token.type == Token::Type::RawQuoted)\n                    return {};\n                return complete_expansion(context, token,\n                                          start + token.pos, cursor_pos,\n                                          pos_in_token - token.pos);\n            }\n        }\n    }\n    return {};\n}\n\n\nstatic Completions requote(Completions completions, Token::Type token_type)\n{\n    if (completions.flags & Completions::Flags::Quoted)\n        return completions;\n\n    if (token_type == Token::Type::Raw)\n    {\n        const bool at_token_start = completions.start == 0;\n        for (auto& candidate : completions.candidates)\n        {\n            const StringView to_escape = \";\\n \\t\";\n            if ((at_token_start and candidate.substr(0_byte, 1_byte) == \"%\") or\n                any_of(candidate, [&](auto c) { return contains(to_escape, c); }))\n                candidate = at_token_start ? quote(candidate) : escape(candidate, to_escape, '\\\\');\n        }\n    }\n    else if (token_type == Token::Type::RawQuoted)\n        completions.flags |= Completions::Flags::Quoted;\n    else\n        kak_assert(false);\n\n    return completions;\n}\n\nCompletions CommandManager::Completer::operator()(\n    const Context& context, StringView command_line, ByteCount cursor_pos)\n{\n    auto prefix = command_line.substr(0_byte, cursor_pos);\n    CommandParser parser{prefix};\n    const char* cursor = prefix.begin() + cursor_pos;\n    Vector<Token> tokens;\n\n    bool is_last_token = true;\n    while (auto token = parser.read_token(false))\n    {\n        if (token->type == Token::Type::CommandSeparator)\n        {\n            tokens.clear();\n            continue;\n        }\n\n        tokens.push_back(std::move(*token));\n        if (parser.pos() >= cursor)\n        {\n            is_last_token = false;\n            break;\n        }\n    }\n\n    if (is_last_token)\n        tokens.push_back({Token::Type::Raw, prefix.length(), {}});\n    kak_assert(not tokens.empty());\n    const auto& token = tokens.back();\n\n    if (token.terminated) // do not complete past explicit token close\n        return Completions{};\n\n    const ByteCount start = token.pos;\n    const ByteCount pos_in_token = cursor_pos - start;\n\n    // command name completion\n    if (tokens.size() == 1 and (token.type == Token::Type::Raw or\n                                token.type == Token::Type::RawQuoted))\n    {\n        return offset_pos(requote(CommandManager::instance().complete_command_name(context, prefix), token.type), start);\n    }\n\n    auto& commands = CommandManager::instance().m_commands;\n\n    switch (token.type)\n    {\n    case Token::Type::RegisterExpand:\n    case Token::Type::OptionExpand:\n    case Token::Type::ShellExpand:\n    case Token::Type::ValExpand:\n    case Token::Type::FileExpand:\n        return complete_expansion(context, token, start, cursor_pos, pos_in_token);\n\n    case Token::Type::Raw:\n    case Token::Type::RawQuoted:\n    {\n        StringView command_name = tokens.front().content;\n        auto command_it = commands.find(resolve_alias(context, command_name));\n        if (command_it == commands.end())\n            return Completions{};\n\n        auto& command = command_it->value;\n        if (command_name != m_last_complete_command)\n        {\n            m_last_complete_command = command_name.str();\n            m_command_completer = command.completer;\n        }\n\n        auto raw_params = tokens | skip(1) | transform(&Token::content) | gather<Vector<String>>();\n        ParametersParser parser{raw_params, command.param_desc, true};\n\n        switch (parser.state())\n        {\n            case ParametersParser::State::Switch:\n            {\n                auto switches = Kakoune::complete(token.content.substr(1_byte), pos_in_token,\n                                                  concatenated(command.param_desc.switches\n                                                                   | transform(&SwitchMap::Item::key)\n                                                                   | filter([&](const auto& key) {\n                                                                       return not parser.get_switch(key);\n                                                                   }),\n                                                               ConstArrayView<String>{\"-\"}));\n                return switches.empty()\n                        ? Completions{}\n                        : Completions{start+1, cursor_pos, std::move(switches), Completions::Flags::Menu};\n            }\n            case ParametersParser::State::SwitchArgument:\n            {\n                const auto& switch_desc = command.param_desc.switches.get(raw_params.at(raw_params.size() - 2).substr(1_byte));\n                if (not *switch_desc.arg_completer)\n                    return Completions{};\n                return offset_pos(requote((*switch_desc.arg_completer)(context, raw_params.back(), pos_in_token), token.type), start);\n            }\n            case ParametersParser::State::Positional:\n                break;\n        }\n\n        if (not m_command_completer)\n            return Completions{};\n\n        Vector<String> params{parser.begin(), parser.end()};\n        auto index = params.size() - 1;\n\n        return offset_pos(requote(m_command_completer(context, params, index, pos_in_token), token.type), start);\n    }\n    case Token::Type::Expand:\n        return complete_expand(context, token.content, start, cursor_pos, pos_in_token);\n    default:\n        break;\n    }\n    return Completions{};\n}\n\nCompletions CommandManager::NestedCompleter::operator()(\n    const Context& context, CommandParameters params,\n    size_t token_to_complete, ByteCount pos_in_token)\n{\n    StringView prefix = params[token_to_complete].substr(0, pos_in_token);\n    if (token_to_complete == 0)\n        return CommandManager::instance().complete_command_name(context, prefix);\n\n    StringView command_name = params[0];\n    auto& commands = CommandManager::instance().m_commands;\n    if (command_name != m_last_complete_command)\n    {\n        m_last_complete_command = command_name.str();\n        auto it = commands.find(resolve_alias(context, command_name));\n        if (it != commands.end())\n            m_command_completer = it->value.completer;\n    }\n\n    return m_command_completer\n        ? m_command_completer(context, params.subrange(1), token_to_complete-1, pos_in_token)\n        : Completions{};\n}\n\nUnitTest test_command_parsing{[]\n{\n    auto check_quoted = [](StringView str, bool terminated, StringView content)\n    {\n        auto check_quoted_impl = [&](auto type_hint) {\n            ParseState state{str, str.begin()};\n            const decltype(type_hint) delimiter = *state.pos++;\n            auto quoted = parse_quoted(state, delimiter);\n            kak_assert(quoted.terminated == terminated);\n            kak_assert(quoted.content == content);\n        };\n        check_quoted_impl(Codepoint{});\n        check_quoted_impl(char{});\n    };\n    check_quoted(\"'abc'\", true, \"abc\");\n    check_quoted(\"'abc''def\", false, \"abc'def\");\n    check_quoted(\"'abc''def'''\", true, \"abc'def'\");\n    check_quoted(StringView(\"'abc''def'\", 5), true, \"abc\");\n\n    auto check_balanced = [](StringView str, bool terminated, StringView content)\n    {\n        ParseState state{str, str.begin()+1};\n        auto quoted = parse_quoted_balanced<'{', '}'>(state);\n        kak_assert(quoted.terminated == terminated);\n        kak_assert(quoted.content == content);\n    };\n    check_balanced(\"{abc}\", true, \"abc\");\n    check_balanced(\"{abc{def}}\", true, \"abc{def}\");\n    check_balanced(\"{{abc}{def}\", false, \"{abc}{def}\");\n\n    auto check_unquoted = [](StringView str, StringView content)\n    {\n        ParseState state{str, str.begin()};\n        kak_assert(parse_unquoted(state) == content);\n    };\n    check_unquoted(\"abc def\", \"abc\");\n    check_unquoted(\"abc; def\", \"abc\");\n    check_unquoted(\"abc\\\\; def\", \"abc;\");\n    check_unquoted(\"abc\\\\;\\\\ def\", \"abc; def\");\n\n    {\n        CommandParser parser(R\"(foo 'bar' \"baz\" qux)\");\n        kak_assert(parser.read_token(false)->content == \"foo\");\n        kak_assert(parser.read_token(false)->content == \"bar\");\n        kak_assert(parser.read_token(false)->content == \"baz\");\n        kak_assert(parser.read_token(false)->content == \"qux\");\n        kak_assert(not parser.read_token(false));\n    }\n}};\n\n}\n"
  },
  {
    "path": "src/command_manager.hh",
    "content": "#ifndef command_manager_hh_INCLUDED\n#define command_manager_hh_INCLUDED\n\n#include \"completion.hh\"\n#include \"array_view.hh\"\n#include \"shell_manager.hh\"\n#include \"parameters_parser.hh\"\n#include \"string.hh\"\n#include \"optional.hh\"\n#include \"utils.hh\"\n#include \"hash_map.hh\"\n#include \"function.hh\"\n\nnamespace Kakoune\n{\n\nclass Context;\nusing CommandParameters = ConstArrayView<String>;\nusing CommandFunc = Function<void (const ParametersParser& parser,\n                                        Context& context,\n                                        const ShellContext& shell_context)>;\n\nusing CommandCompleter = Function<Completions (const Context& context,\n                                                    CommandParameters,\n                                                    size_t, ByteCount)>;\n\nusing CommandHelper = Function<String (const Context& context, CommandParameters)>;\n\nenum class CommandFlags\n{\n    None   = 0,\n    Hidden = 1,\n};\nconstexpr bool with_bit_ops(Meta::Type<CommandFlags>) { return true; }\n\nstruct CommandInfo { String name, info; };\n\nstruct Token\n{\n    enum class Type\n    {\n        Raw,\n        RawQuoted,\n        Expand,\n        ShellExpand,\n        RegisterExpand,\n        OptionExpand,\n        ValExpand,\n        ArgExpand,\n        FileExpand,\n        CommandSeparator\n    };\n\n    Type type;\n    ByteCount pos;\n    String content;\n    bool terminated = false;\n};\n\nstruct ParseState\n{\n    StringView str;\n    const char* pos;\n\n    operator bool() const { return pos != str.end(); }\n};\n\nclass CommandParser\n{\npublic:\n    CommandParser(StringView command_line);\n    Optional<Token> read_token(bool throw_on_unterminated);\n\n    const char* pos() const { return m_state.pos; }\n    bool done() const { return not m_state; }\n\nprivate:\n    ParseState m_state;\n};\n\nclass CommandManager : public Singleton<CommandManager>\n{\npublic:\n    void execute(StringView command_line, Context& context,\n                 const ShellContext& shell_context = ShellContext{});\n\n    void execute_single_command(CommandParameters params,\n                                Context& context,\n                                const ShellContext& shell_context);\n\n\n    Optional<CommandInfo> command_info(const Context& context,\n                                       StringView command_line) const;\n\n    bool command_defined(StringView command_name) const;\n\n    void register_command(String command_name, CommandFunc func,\n                          String docstring,\n                          ParameterDesc param_desc,\n                          CommandFlags flags = CommandFlags::None,\n                          CommandHelper helper = CommandHelper(),\n                          CommandCompleter completer = CommandCompleter());\n\n    void set_command_completer(StringView command_name, CommandCompleter completer);\n\n    Completions complete_command_name(const Context& context, StringView query) const;\n\n    bool module_defined(StringView module_name) const;\n\n    void register_module(String module_name, String commands);\n\n    void load_module(StringView module_name, Context& context);\n    HashSet<String> loaded_modules() const;\n\n    Completions complete_module_name(StringView query) const;\n\n    struct Completer\n    {\n        Completions operator()(const Context& context,\n                              StringView command_line, ByteCount cursor_pos);\n\n    private:\n        String m_last_complete_command;\n        CommandCompleter m_command_completer;\n    };\n\n    struct NestedCompleter\n    {\n        Completions operator()(const Context& context, CommandParameters params,\n                               size_t token_to_complete, ByteCount pos_in_token);\n\n    private:\n        String m_last_complete_command;\n        CommandCompleter m_command_completer;\n    };\n\nprivate:\n    struct Command\n    {\n        CommandFunc func;\n        String docstring;\n        ParameterDesc param_desc;\n        CommandFlags flags;\n        CommandHelper helper;\n        CommandCompleter completer;\n    };\n    using CommandMap = HashMap<String, Command, MemoryDomain::Commands>;\n    CommandMap m_commands;\n    int m_command_depth = 0;\n\n    struct Module\n    {\n        enum class State\n        {\n            Registered,\n            Loading,\n            Loaded\n        };\n        State state = State::Registered;\n        String commands;\n    };\n    using ModuleMap = HashMap<String, Module, MemoryDomain::Commands>;\n    ModuleMap m_modules;\n};\n\nString expand(StringView str, const Context& context,\n              const ShellContext& shell_context = ShellContext{});\n\nString expand(StringView str, const Context& context,\n              const ShellContext& shell_context,\n              const FunctionRef<String (String)>& postprocess);\n\n}\n\n#endif // command_manager_hh_INCLUDED\n"
  },
  {
    "path": "src/commands.cc",
    "content": "#include \"commands.hh\"\n\n#include \"alias_registry.hh\"\n#include \"buffer.hh\"\n#include \"buffer_manager.hh\"\n#include \"buffer_utils.hh\"\n#include \"client.hh\"\n#include \"client_manager.hh\"\n#include \"command_manager.hh\"\n#include \"completion.hh\"\n#include \"context.hh\"\n#include \"debug.hh\"\n#include \"event_manager.hh\"\n#include \"face_registry.hh\"\n#include \"file.hh\"\n#include \"hash_map.hh\"\n#include \"hook_manager.hh\"\n#include \"highlighter.hh\"\n#include \"highlighters.hh\"\n#include \"input_handler.hh\"\n#include \"insert_completer.hh\"\n#include \"keymap_manager.hh\"\n#include \"normal.hh\"\n#include \"option_manager.hh\"\n#include \"option_types.hh\"\n#include \"parameters_parser.hh\"\n#include \"profile.hh\"\n#include \"ranges.hh\"\n#include \"ranked_match.hh\"\n#include \"regex.hh\"\n#include \"register_manager.hh\"\n#include \"remote.hh\"\n#include \"shell_manager.hh\"\n#include \"string.hh\"\n#include \"user_interface.hh\"\n#include \"window.hh\"\n\n#include <utility>\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n\n#if defined(__GLIBC__) || defined(__CYGWIN__)\n#include <malloc.h>\n#endif\n\nnamespace Kakoune\n{\n\nextern const char* version;\n\nstruct LocalScope : Scope\n{\n    LocalScope(Context& context)\n        : Scope(context.scope()), m_context{context}\n    {\n        m_context.m_local_scopes.push_back(this);\n    }\n\n    ~LocalScope()\n    {\n        kak_assert(not m_context.m_local_scopes.empty() and m_context.m_local_scopes.back() == this);\n        m_context.m_local_scopes.pop_back();\n    }\n\nprivate:\n    Context& m_context;\n};\n\nnamespace\n{\n\nBuffer* open_fifo(StringView name, StringView filename, Buffer::Flags flags, bool scroll)\n{\n    int fd = open(parse_filename(filename).c_str(), O_RDONLY | O_NONBLOCK);\n    fcntl(fd, F_SETFD, FD_CLOEXEC);\n    if (fd < 0)\n       throw runtime_error(format(\"unable to open '{}'\", filename));\n\n    return create_fifo_buffer(name.str(), fd, flags, scroll ? AutoScroll::Yes : AutoScroll::No);\n}\n\ntemplate<typename... Completers> struct PerArgumentCommandCompleter;\n\ntemplate<> struct PerArgumentCommandCompleter<>\n{\n    Completions operator()(const Context&, CommandParameters,\n                           size_t, ByteCount) const { return {}; }\n};\n\ntemplate<typename Completer, typename... Rest>\nstruct PerArgumentCommandCompleter<Completer, Rest...> : PerArgumentCommandCompleter<Rest...>\n{\n    template<typename C, typename... R>\n        requires (not std::is_base_of_v<PerArgumentCommandCompleter<>, std::remove_reference_t<C>>)\n    PerArgumentCommandCompleter(C&& completer, R&&... rest)\n      : PerArgumentCommandCompleter<Rest...>(std::forward<R>(rest)...),\n        m_completer(std::forward<C>(completer)) {}\n\n    Completions operator()(const Context& context,\n                           CommandParameters params, size_t token_to_complete,\n                           ByteCount pos_in_token)\n    {\n        if (token_to_complete == 0)\n        {\n            const String& arg = token_to_complete < params.size() ?\n                                params[token_to_complete] : String();\n            return m_completer(context, arg, pos_in_token);\n        }\n        return PerArgumentCommandCompleter<Rest...>::operator()(\n            context, params.subrange(1),\n            token_to_complete-1, pos_in_token);\n    }\n\n    Completer m_completer;\n};\n\ntemplate<typename... Completers>\nPerArgumentCommandCompleter<std::decay_t<Completers>...>\nmake_completer(Completers&&... completers)\n{\n    return {std::forward<Completers>(completers)...};\n}\n\ntemplate<typename Completer>\nauto add_flags(Completer completer, Completions::Flags completions_flags)\n{\n    return [completer=std::move(completer), completions_flags]\n           (const Context& context, StringView prefix, ByteCount cursor_pos) {\n        Completions res = completer(context, prefix, cursor_pos);\n        res.flags |= completions_flags;\n        return res;\n    };\n}\n\ntemplate<typename Completer>\nauto menu(Completer completer)\n{\n    return add_flags(std::move(completer), Completions::Flags::Menu);\n}\n\ntemplate<bool menu>\nauto filename_completer = make_completer(\n    [](const Context& context, StringView prefix, ByteCount cursor_pos)\n    { return Completions{ 0_byte, cursor_pos,\n                          complete_filename(prefix,\n                                            context.options()[\"ignored_files\"].get<Regex>(),\n                                            cursor_pos, FilenameFlags::Expand),\n                                            menu ? Completions::Flags::Menu : Completions::Flags::None}; });\n\ntemplate<bool menu>\nauto filename_arg_completer =\n    [](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions\n    { return { 0_byte, cursor_pos,\n               complete_filename(prefix,\n                                 context.options()[\"ignored_files\"].get<Regex>(),\n                                 cursor_pos, FilenameFlags::OnlyDirectories),\n               menu ? Completions::Flags::Menu : Completions::Flags::None }; };\n\nauto client_arg_completer =\n    [](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions\n    { return { 0_byte, cursor_pos,\n               ClientManager::instance().complete_client_name(prefix, cursor_pos),\n               Completions::Flags::Menu }; };\n\nauto arg_completer = [](auto candidates) -> PromptCompleter {\n    return [=](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions {\n        return Completions{ 0_byte, cursor_pos, complete(prefix, cursor_pos, candidates), Completions::Flags::Menu };\n    };\n};\n\ntemplate<bool ignore_current = false>\nstatic Completions complete_buffer_name(const Context& context, StringView prefix, ByteCount cursor_pos)\n{\n    struct RankedMatchAndBuffer : RankedMatch\n    {\n        RankedMatchAndBuffer(RankedMatch  m, const Buffer* b)\n            : RankedMatch{std::move(m)}, buffer{b} {}\n\n        using RankedMatch::operator==;\n        using RankedMatch::operator<;\n\n        const Buffer* buffer;\n    };\n\n    StringView query = prefix.substr(0, cursor_pos);\n    Vector<RankedMatchAndBuffer> filename_matches;\n    Vector<RankedMatchAndBuffer> matches;\n    for (const auto& buffer : BufferManager::instance())\n    {\n        if (ignore_current and buffer.get() == &context.buffer())\n            continue;\n\n        StringView bufname = buffer->display_name();\n        if (buffer->flags() & Buffer::Flags::File)\n        {\n            if (RankedMatch match{split_path(bufname).second, query})\n            {\n                filename_matches.emplace_back(match, buffer.get());\n                continue;\n            }\n        }\n        if (RankedMatch match{bufname, query})\n            matches.emplace_back(match, buffer.get());\n    }\n    std::sort(filename_matches.begin(), filename_matches.end());\n    std::sort(matches.begin(), matches.end());\n\n    CandidateList res;\n    for (auto& match : filename_matches)\n        res.push_back(match.buffer->display_name());\n    for (auto& match : matches)\n        res.push_back(match.buffer->display_name());\n\n    return { 0, cursor_pos, res };\n}\n\ntemplate<typename Func>\nauto make_single_word_completer(Func&& func)\n{\n    return make_completer(\n        [func = std::move(func)](const Context& context,\n               StringView prefix, ByteCount cursor_pos) -> Completions {\n            auto candidate = { func(context) };\n            return { 0_byte, cursor_pos, complete(prefix, cursor_pos, candidate) }; });\n}\n\nconst ParameterDesc no_params{ {}, ParameterDesc::Flags::None, 0, 0 };\nconst ParameterDesc single_param{ {}, ParameterDesc::Flags::None, 1, 1 };\nconst ParameterDesc single_optional_param{ {}, ParameterDesc::Flags::None, 0, 1 };\nconst ParameterDesc double_params{ {}, ParameterDesc::Flags::None, 2, 2 };\n\nstatic Completions complete_scope(const Context&,\n                                  StringView prefix, ByteCount cursor_pos)\n{\n   static constexpr StringView scopes[] = { \"global\", \"buffer\", \"window\", \"local\"};\n   return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) };\n}\n\nstatic Completions complete_scope_including_current(const Context&,\n                                  StringView prefix, ByteCount cursor_pos)\n{\n   static constexpr StringView scopes[] = { \"global\", \"buffer\", \"window\", \"local\", \"current\" };\n   return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) };\n}\n\nstatic Completions complete_scope_no_global(const Context&,\n                                            StringView prefix, ByteCount cursor_pos)\n{\n   static constexpr StringView scopes[] = { \"buffer\", \"window\", \"local\", \"current\" };\n   return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) };\n}\n\n\nstatic Completions complete_command_name(const Context& context,\n                                         StringView prefix, ByteCount cursor_pos)\n{\n   return CommandManager::instance().complete_command_name(\n       context, prefix.substr(0, cursor_pos));\n}\n\nstruct AsyncShellScript\n{\n    AsyncShellScript(String shell_script,\n                     Completions::Flags flags = Completions::Flags::None)\n      : m_shell_script{std::move(shell_script)}, m_flags(flags) {}\n\n    AsyncShellScript(const AsyncShellScript& other) : m_shell_script{other.m_shell_script}, m_flags(other.m_flags) {}\n    AsyncShellScript& operator=(const AsyncShellScript& other) {  m_shell_script = other.m_shell_script; m_flags = other.m_flags; return *this; }\n\nprotected:\n    void spawn_script(const Context& context, const ShellContext& shell_context, auto&& handle_line)\n    {\n        m_handle_line = handle_line;\n        m_running_script.emplace(ShellManager::instance().spawn(m_shell_script, context, false, shell_context));\n        m_watcher.emplace((int)m_running_script->out, FdEvents::Read, EventMode::Urgent,\n                          [this, &input_handler=context.input_handler()](auto&&... args) { read_stdout(input_handler); });\n    }\n\n    void read_stdout(InputHandler& input_handler)\n    {\n        char buffer[2048];\n        bool closed = false;\n        int fd = (int)m_running_script->out;\n        while (fd_readable(fd))\n        {\n            int size = read(fd, buffer, sizeof(buffer));\n            if (size == 0)\n            {\n                closed = true;\n                break;\n            }\n            m_stdout_buffer.insert(m_stdout_buffer.end(), buffer, buffer + size);\n        }\n        auto end = closed ? m_stdout_buffer.end() : find(m_stdout_buffer | reverse(), '\\n').base();\n        for (auto c : ArrayView(m_stdout_buffer.begin(), end) | split<StringView>('\\n')\n                                                              | filter([](auto s) { return not s.empty(); }))\n            m_handle_line(c);\n\n        m_stdout_buffer.erase(m_stdout_buffer.begin(), end);\n\n        input_handler.refresh_ifn();\n        if (closed)\n        {\n            m_running_script.reset();\n            m_watcher.reset();\n            m_handle_line = {};\n        }\n    }\n\n    String m_shell_script;\n    Optional<Shell> m_running_script;\n    Optional<FDWatcher> m_watcher;\n    Vector<char, MemoryDomain::Completion> m_stdout_buffer;\n    Function<void (StringView)> m_handle_line;\n    Completions::Flags m_flags;\n};\n\nstruct ShellScriptCompleter : AsyncShellScript\n{\n    using AsyncShellScript::AsyncShellScript;\n\n    Completions operator()(const Context& context,\n                           CommandParameters params, size_t token_to_complete,\n                           ByteCount pos_in_token)\n    {\n        CandidateList candidates;\n        if (m_last_token != token_to_complete or pos_in_token != m_last_pos_in_token)\n        {\n            ShellContext shell_context{\n                params,\n                { { \"token_to_complete\", to_string(token_to_complete) },\n                  { \"pos_in_token\",      to_string(pos_in_token) } }\n            };\n            spawn_script(context, shell_context, [this](StringView line) { m_candidates.push_back(line.str()); });\n\n            candidates = std::move(m_candidates); // avoid completion menu flicker by keeping the previous result visible\n            m_candidates.clear();\n            m_last_token = token_to_complete;\n            m_last_pos_in_token = pos_in_token;\n        }\n        else\n            candidates = m_candidates;\n\n        return {0_byte, pos_in_token, std::move(candidates), m_flags};\n    }\n\nprivate:\n    CandidateList m_candidates;\n    int m_last_token = -1;\n    ByteCount m_last_pos_in_token = -1;\n};\n\nstruct ShellCandidatesCompleter : AsyncShellScript\n{\n    using AsyncShellScript::AsyncShellScript;\n\n    Completions operator()(const Context& context,\n                           CommandParameters params, size_t token_to_complete,\n                           ByteCount pos_in_token)\n    {\n        if (m_last_token != token_to_complete)\n        {\n            ShellContext shell_context{\n                params,\n                { { \"token_to_complete\", to_string(token_to_complete) } }\n            };\n            spawn_script(context, shell_context, [this](StringView line) { m_candidates.emplace_back(line.str(), used_letters(line)); });\n            m_candidates.clear();\n            m_last_token = token_to_complete;\n        }\n        return rank_candidates(params[token_to_complete].substr(0, pos_in_token));\n    }\n\nprivate:\n    Completions rank_candidates(StringView query)\n    {\n        UsedLetters query_letters = used_letters(query);\n        Vector<RankedMatch> matches;\n        for (auto&& [i, candidate] : m_candidates | enumerate())\n        {\n            if (RankedMatch m{candidate.first, candidate.second, query, query_letters})\n            {\n                m.set_input_sequence_number(i);\n                matches.push_back(m);\n            }\n        }\n\n        constexpr size_t max_count = 100;\n        CandidateList res;\n        // Gather best max_count matches\n        for_n_best(matches, max_count, [](auto& lhs, auto& rhs) { return rhs < lhs; }, [&] (const RankedMatch& m) {\n            if (not res.empty() and res.back() == m.candidate())\n                return false;\n            res.push_back(m.candidate().str());\n            return true;\n        });\n\n        return Completions{0_byte, query.length(), std::move(res), m_flags};\n    }\n\n    Vector<std::pair<String, UsedLetters>, MemoryDomain::Completion> m_candidates;\n    int m_last_token = -1;\n};\n\ntemplate<typename Completer>\nstruct PromptCompleterAdapter\n{\n    PromptCompleterAdapter(Completer completer) : m_completer{std::move(completer)} {}\n\n    operator PromptCompleter() &&\n    {\n        if (not m_completer)\n            return {};\n        return [completer=std::move(m_completer)](const Context& context,\n                                                  StringView prefix, ByteCount cursor_pos) {\n            return completer(context, {String{String::NoCopy{}, prefix}}, 0, cursor_pos);\n        };\n    }\n\nprivate:\n    Completer m_completer;\n};\n\nScope* get_scope_ifp(StringView scope, const Context& context)\n{\n    if (prefix_match(\"global\", scope))\n        return &GlobalScope::instance();\n    else if (prefix_match(\"buffer\", scope))\n        return &context.buffer();\n    else if (prefix_match(\"window\", scope))\n        return &context.window();\n    else if (prefix_match(\"local\", scope))\n        return context.local_scope();\n    else if (prefix_match(scope, \"buffer=\"))\n        return &BufferManager::instance().get_buffer(scope.substr(7_byte));\n    return nullptr;\n}\n\nScope& get_scope(StringView scope, const Context& context)\n{\n    if (auto s = get_scope_ifp(scope, context))\n        return *s;\n    throw runtime_error(format(\"no such scope: '{}'\", scope));\n}\n\nstruct CommandDesc\n{\n    const char* name;\n    const char* alias;\n    const char* docstring;\n    ParameterDesc params;\n    CommandFlags flags;\n    CommandHelper helper;\n    CommandCompleter completer;\n    void (*func)(const ParametersParser&, Context&, const ShellContext&);\n};\n\ntemplate<bool force_reload>\nvoid edit(const ParametersParser& parser, Context& context, const ShellContext&)\n{\n    const bool scratch = (bool)parser.get_switch(\"scratch\");\n\n    if (parser.positional_count() == 0 and not force_reload and not scratch)\n        throw wrong_argument_count();\n\n    const bool no_hooks = context.hooks_disabled();\n    const auto flags = (no_hooks ? Buffer::Flags::NoHooks : Buffer::Flags::None) |\n       (parser.get_switch(\"debug\") ? Buffer::Flags::Debug : Buffer::Flags::None);\n\n    auto& buffer_manager = BufferManager::instance();\n    const auto& name = parser.positional_count() > 0 ?\n        parser[0] : (scratch ? generate_buffer_name(\"*scratch-{}*\") : context.buffer().name());\n\n    Buffer* buffer = buffer_manager.get_buffer_ifp(name);\n    if (scratch)\n    {\n        if (parser.get_switch(\"readonly\") or parser.get_switch(\"fifo\") or parser.get_switch(\"scroll\"))\n            throw runtime_error(\"scratch is not compatible with readonly, fifo or scroll\");\n\n        if (buffer == nullptr or force_reload)\n        {\n            if (buffer != nullptr and force_reload)\n                buffer_manager.delete_buffer(*buffer);\n            buffer = create_buffer_from_string(name, flags, {});\n        }\n        else if (buffer->flags() & Buffer::Flags::File)\n            throw runtime_error(format(\"buffer '{}' exists but is not a scratch buffer\", name));\n    }\n    else if (force_reload and buffer and buffer->flags() & Buffer::Flags::File)\n    {\n        reload_file_buffer(*buffer);\n    }\n    else\n    {\n        if (auto fifo = parser.get_switch(\"fifo\"))\n            buffer = open_fifo(name, *fifo, flags, (bool)parser.get_switch(\"scroll\"));\n        else if (not buffer)\n        {\n            buffer = parser.get_switch(\"existing\") ? open_file_buffer(name, flags)\n                                                   : open_or_create_file_buffer(name, flags);\n            if (buffer->flags() & Buffer::Flags::New)\n                context.print_status({format(\"new file '{}'\", name), context.faces()[\"StatusLine\"]});\n        }\n\n        buffer->flags() &= ~Buffer::Flags::NoHooks;\n        if (parser.get_switch(\"readonly\"))\n        {\n            buffer->flags() |= Buffer::Flags::ReadOnly;\n            buffer->options().get_local_option(\"readonly\").set(true);\n        }\n    }\n\n    Buffer* current_buffer = context.has_buffer() ? &context.buffer() : nullptr;\n\n    const size_t param_count = parser.positional_count();\n    if (current_buffer and (buffer != current_buffer or param_count > 1))\n        context.push_jump();\n\n    if (buffer != current_buffer)\n        context.change_buffer(*buffer);\n    buffer = &context.buffer(); // change_buffer hooks might change the buffer again\n\n    if (parser.get_switch(\"fifo\") and not parser.get_switch(\"scroll\"))\n        context.selections_write_only() = { *buffer, Selection{} };\n    else if (param_count > 1 and not parser[1].empty())\n    {\n        int line = std::max(0, str_to_int(parser[1]) - 1);\n        int column = param_count > 2 and not parser[2].empty() ?\n                     std::max(0, str_to_int(parser[2]) - 1) : 0;\n\n        auto& buffer = context.buffer();\n        context.selections_write_only() = { buffer, buffer.clamp({ line,  column }) };\n        if (context.has_window())\n            context.window().center_line(context.selections().main().cursor().line);\n    }\n}\n\nParameterDesc edit_params{\n    { { \"existing\", { {}, \"fail if the file does not exist, do not open a new file\" } },\n      { \"scratch\",  { {}, \"create a scratch buffer, not linked to a file\" } },\n      { \"debug\",    { {}, \"create buffer as debug output\" } },\n      { \"fifo\",     { {filename_arg_completer<true>},  \"create a buffer reading its content from a named fifo\" } },\n      { \"readonly\", { {}, \"create a buffer in readonly mode\" } },\n      { \"scroll\",   { {}, \"place the initial cursor so that the fifo will scroll to show new data\" } } },\n      ParameterDesc::Flags::None, 0, 3\n};\nconst CommandDesc edit_cmd = {\n    \"edit\",\n    \"e\",\n    \"edit [<switches>] <filename> [<line> [<column>]]: open the given filename in a buffer\",\n    edit_params,\n    CommandFlags::None,\n    CommandHelper{},\n    filename_completer<false>,\n    edit<false>\n};\n\nconst CommandDesc force_edit_cmd = {\n    \"edit!\",\n    \"e!\",\n    \"edit! [<switches>] <filename> [<line> [<column>]]: open the given filename in a buffer, \"\n    \"force reload if needed\",\n    edit_params,\n    CommandFlags::None,\n    CommandHelper{},\n    filename_completer<false>,\n    edit<true>\n};\n\nconst ParameterDesc write_params = {\n    {\n        { \"sync\", { {}, \"force the synchronization of the file onto the filesystem\" } },\n        { \"method\", { {arg_completer(Array{\"replace\", \"overwrite\"})}, \"explicit writemethod (replace|overwrite)\" } },\n        { \"force\", { {}, \"Allow overwriting existing file with explicit filename\" } }\n    },\n    ParameterDesc::Flags::None, 0, 1\n};\n\nconst ParameterDesc write_params_except_force = {\n    {\n        { \"sync\", { {}, \"force the synchronization of the file onto the filesystem\" } },\n        { \"method\", { {arg_completer(Array{\"replace\", \"overwrite\"})}, \"explicit writemethod (replace|overwrite)\" } },\n    },\n    ParameterDesc::Flags::None, 0, 1\n};\n\nauto parse_write_method(StringView str)\n{\n    constexpr auto desc = enum_desc(Meta::Type<WriteMethod>{});\n    auto it = find_if(desc, [str](const EnumDesc<WriteMethod>& d) { return d.name == str; });\n    if (it == desc.end())\n        throw runtime_error(format(\"invalid writemethod '{}'\", str));\n    return it->value;\n}\n\nvoid do_write_buffer(Context& context, Optional<String> filename, WriteFlags flags, Optional<WriteMethod> write_method = {})\n{\n    Buffer& buffer = context.buffer();\n    const bool is_file = (bool)(buffer.flags() & Buffer::Flags::File);\n\n    if (not filename and !is_file)\n        throw runtime_error(\"cannot write a non file buffer without a filename\");\n\n    const bool is_readonly = (bool)(context.buffer().flags() & Buffer::Flags::ReadOnly);\n    // if the buffer is in read-only mode and we try to save it directly\n    // or we try to write to it indirectly using e.g. a symlink, throw an error\n    if (is_file and is_readonly and\n        (not filename or real_path(*filename) == buffer.filename()))\n        throw runtime_error(\"cannot overwrite the buffer when in readonly mode\");\n\n    auto effective_filename = filename ? parse_filename(*filename) : buffer.filename();\n    if (filename and not (flags & WriteFlags::Force) and\n        real_path(effective_filename) != buffer.filename() and\n        regular_file_exists(effective_filename))\n        throw runtime_error(\"cannot overwrite existing file without -force\");\n\n    auto method = write_method.value_or_compute([&] { return context.options()[\"writemethod\"].get<WriteMethod>(); });\n\n    context.hooks().run_hook(Hook::BufWritePre, effective_filename, context);\n    BusyIndicator busy_indicator{context, [&](std::chrono::seconds elapsed) {\n        return DisplayLine{format(\"waiting while writing buffer '{}' ({}s)\", buffer.filename(), elapsed.count()),\n                           context.faces()[\"Information\"]};\n    }};\n    write_buffer_to_file(buffer, effective_filename, method, flags);\n    context.hooks().run_hook(Hook::BufWritePost, effective_filename, context);\n}\n\ntemplate<bool force = false>\nvoid write_buffer(const ParametersParser& parser, Context& context, const ShellContext&)\n{\n    return do_write_buffer(context,\n                           parser.positional_count() > 0 ? parser[0] : Optional<String>{},\n                           (parser.get_switch(\"sync\") ? WriteFlags::Sync : WriteFlags::None) |\n                           (parser.get_switch(\"force\") or force ? WriteFlags::Force : WriteFlags::None),\n                           parser.get_switch(\"method\").map(parse_write_method));\n}\n\nconst CommandDesc write_cmd = {\n    \"write\",\n    \"w\",\n    \"write [<switches>] [<filename>]: write the current buffer to its file \"\n    \"or to <filename> if specified\",\n    write_params,\n    CommandFlags::None,\n    CommandHelper{},\n    filename_completer<false>,\n    write_buffer,\n};\n\nconst CommandDesc force_write_cmd = {\n    \"write!\",\n    \"w!\",\n    \"write! [<switches>] [<filename>]: write the current buffer to its file \"\n    \"or to <filename> if specified, even when the file is write protected\",\n    write_params_except_force,\n    CommandFlags::None,\n    CommandHelper{},\n    filename_completer<false>,\n    write_buffer<true>,\n};\n\nvoid write_all_buffers(const Context& context, bool sync = false, Optional<WriteMethod> write_method = {})\n{\n    // Copy buffer list because hooks might be creating/deleting buffers\n    Vector<SafePtr<Buffer>> buffers;\n    for (auto& buffer : BufferManager::instance())\n        buffers.emplace_back(buffer.get());\n\n    for (auto& buffer : buffers)\n    {\n        if ((buffer->flags() & Buffer::Flags::File) and\n            ((buffer->flags() & Buffer::Flags::New) or\n             buffer->is_modified())\n            and !(buffer->flags() & Buffer::Flags::ReadOnly))\n        {\n            auto method = write_method.value_or_compute([&] { return context.options()[\"writemethod\"].get<WriteMethod>(); });\n            auto flags = sync ? WriteFlags::Sync : WriteFlags::None;\n            buffer->run_hook_in_own_context(Hook::BufWritePre, buffer->name(), context.name());\n            BusyIndicator busy_indicator{context, [&](std::chrono::seconds elapsed) {\n                return DisplayLine{format(\"waiting while writing buffer ({}s)\", elapsed.count()),\n                                   context.faces()[\"Information\"]};\n            }};\n            write_buffer_to_file(*buffer, buffer->name(), method, flags);\n            buffer->run_hook_in_own_context(Hook::BufWritePost, buffer->name(), context.name());\n        }\n    }\n}\n\nconst CommandDesc write_all_cmd = {\n    \"write-all\",\n    \"wa\",\n    \"write-all [<switches>]: write all changed buffers that are associated to a file\",\n    ParameterDesc{\n        write_params_except_force.switches,\n        ParameterDesc::Flags::None, 0, 0\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext&){\n        write_all_buffers(context,\n                          (bool)parser.get_switch(\"sync\"),\n                          parser.get_switch(\"method\").map(parse_write_method));\n    }\n};\n\nstatic void ensure_all_buffers_are_saved(Context& context)\n{\n    auto is_modified = [](const UniquePtr<Buffer>& buf) {\n        return (buf->flags() & Buffer::Flags::File) and buf->is_modified();\n    };\n\n    auto it = find_if(BufferManager::instance(), is_modified);\n    const auto end = BufferManager::instance().end();\n    if (it == end)\n        return;\n\n    if (not context.buffer().is_modified())\n    {\n        context.push_jump();\n        context.change_buffer(**it);\n    }\n\n    String message = format(\"{} modified buffers remaining: [\",\n                            std::count_if(it, end, is_modified));\n    while (it != end)\n    {\n        message += (*it)->display_name();\n        it = std::find_if(it+1, end, is_modified);\n        message += (it != end) ? \", \" : \"]\";\n    }\n    throw runtime_error(message);\n}\n\ntemplate<bool force>\nvoid kill(const ParametersParser& parser, Context& context, const ShellContext&)\n{\n    auto& client_manager = ClientManager::instance();\n\n    if (not force)\n        ensure_all_buffers_are_saved(context);\n\n    const int status = parser.positional_count() > 0 ? str_to_int(parser[0]) : 0;\n    while (not client_manager.empty())\n        client_manager.remove_client(**client_manager.begin(), true, status);\n\n    throw kill_session{status};\n}\n\nconst CommandDesc kill_cmd = {\n    \"kill\",\n    nullptr,\n    \"kill [<exit status>]: terminate the current session, the server and all clients connected. \"\n    \"An optional integer parameter can set the server and client processes exit status\",\n    { {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 1 },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    kill<false>\n};\n\n\nconst CommandDesc force_kill_cmd = {\n    \"kill!\",\n    nullptr,\n    \"kill! [<exit status>]: force the termination of the current session, the server and all clients connected. \"\n    \"An optional integer parameter can set the server and client processes exit status\",\n    { {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 1 },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    kill<true>\n};\n\nconst CommandDesc daemonize_session_cmd = {\n    \"daemonize-session\",\n    nullptr,\n    \"daemonize-session: set the session server not to quit on last client exit\",\n    { {} },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser&, Context&, const ShellContext&) { Server::instance().daemonize(); }\n};\n\ntemplate<bool force>\nvoid quit(const ParametersParser& parser, Context& context, const ShellContext&)\n{\n    if (not force and ClientManager::instance().count() == 1 and not Server::instance().is_daemon())\n        ensure_all_buffers_are_saved(context);\n\n    const int status = parser.positional_count() > 0 ? str_to_int(parser[0]) : 0;\n    ClientManager::instance().remove_client(context.client(), true, status);\n}\n\nconst CommandDesc quit_cmd = {\n    \"quit\",\n    \"q\",\n    \"quit [<exit status>]: quit current client, and the kakoune session if the client is the last \"\n    \"(if not running in daemon mode). \"\n    \"An optional integer parameter can set the client exit status\",\n    { {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 1 },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    quit<false>\n};\n\nconst CommandDesc force_quit_cmd = {\n    \"quit!\",\n    \"q!\",\n    \"quit! [<exit status>]: quit current client, and the kakoune session if the client is the last \"\n    \"(if not running in daemon mode). Force quit even if the client is the \"\n    \"last and some buffers are not saved. \"\n    \"An optional integer parameter can set the client exit status\",\n    { {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 1 },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    quit<true>\n};\n\ntemplate<bool force>\nvoid write_quit(const ParametersParser& parser, Context& context,\n                const ShellContext& shell_context)\n{\n    do_write_buffer(context, {},\n                    parser.get_switch(\"sync\") ? WriteFlags::Sync : WriteFlags::None,\n                    parser.get_switch(\"method\").map(parse_write_method));\n    quit<force>(parser, context, shell_context);\n}\n\nconst CommandDesc write_quit_cmd = {\n    \"write-quit\",\n    \"wq\",\n    \"write-quit [<switches>] [<exit status>]: write current buffer and quit current client. \"\n    \"An optional integer parameter can set the client exit status\",\n    write_params_except_force,\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    write_quit<false>\n};\n\nconst CommandDesc force_write_quit_cmd = {\n    \"write-quit!\",\n    \"wq!\",\n    \"write-quit! [<switches>] [<exit status>] write: current buffer and quit current client, even if other buffers are not saved. \"\n    \"An optional integer parameter can set the client exit status\",\n    write_params_except_force,\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    write_quit<true>\n};\n\nconst CommandDesc write_all_quit_cmd = {\n    \"write-all-quit\",\n    \"waq\",\n    \"write-all-quit [<switches>] [<exit status>]: write all buffers associated to a file and quit current client. \"\n    \"An optional integer parameter can set the client exit status.\",\n    write_params_except_force,\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext& shell_context)\n    {\n        write_all_buffers(context,\n                          (bool)parser.get_switch(\"sync\"),\n                          parser.get_switch(\"method\").map(parse_write_method));\n        quit<false>(parser, context, shell_context);\n    }\n};\n\nconst CommandDesc buffer_cmd = {\n    \"buffer\",\n    \"b\",\n    \"buffer <name>: set buffer to edit in current client\",\n    {\n        { { \"matching\", { {}, \"treat the argument as a regex\" } } },\n        ParameterDesc::Flags::None, 1, 1\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(menu(complete_buffer_name<true>)),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        Buffer& buffer = parser.get_switch(\"matching\") ? BufferManager::instance().get_buffer_matching(\n                                                             [re=Regex{parser[0]}](Buffer& buffer) {\n                                                                auto name = buffer.name();\n                                                                return regex_match(name.begin(), name.end(), re);\n                                                             })\n                                                       : BufferManager::instance().get_buffer(parser[0]);\n        if (&buffer != &context.buffer())\n        {\n            context.push_jump();\n            context.change_buffer(buffer);\n        }\n    }\n};\n\ntemplate<bool next>\nvoid cycle_buffer(const ParametersParser& parser, Context& context, const ShellContext&)\n{\n    Buffer* oldbuf = &context.buffer();\n    auto it = find_if(BufferManager::instance(),\n                      [oldbuf](const UniquePtr<Buffer>& lhs)\n                      { return lhs.get() == oldbuf; });\n    kak_assert(it != BufferManager::instance().end());\n\n    Buffer* newbuf = nullptr;\n    auto cycle = [&] {\n        if (not next)\n        {\n            if (it == BufferManager::instance().begin())\n                it = BufferManager::instance().end();\n            --it;\n        }\n        else\n        {\n            if (++it == BufferManager::instance().end())\n                it = BufferManager::instance().begin();\n        }\n        newbuf = it->get();\n    };\n    cycle();\n    while (newbuf != oldbuf and newbuf->flags() & Buffer::Flags::Debug)\n        cycle();\n\n    if (newbuf != oldbuf)\n    {\n        context.push_jump();\n        context.change_buffer(*newbuf);\n    }\n}\n\nconst CommandDesc buffer_next_cmd = {\n    \"buffer-next\",\n    \"bn\",\n    \"buffer-next: move to the next buffer in the list\",\n    no_params,\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    cycle_buffer<true>\n};\n\nconst CommandDesc buffer_previous_cmd = {\n    \"buffer-previous\",\n    \"bp\",\n    \"buffer-previous: move to the previous buffer in the list\",\n    no_params,\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    cycle_buffer<false>\n};\n\ntemplate<bool force>\nvoid delete_buffer(const ParametersParser& parser, Context& context, const ShellContext&)\n{\n    BufferManager& manager = BufferManager::instance();\n    Buffer& buffer = parser.positional_count() == 0 ? context.buffer() : manager.get_buffer(parser[0]);\n    if (not force and (buffer.flags() & Buffer::Flags::File) and buffer.is_modified())\n        throw runtime_error(format(\"buffer '{}' is modified\", buffer.display_name()));\n\n    manager.delete_buffer(buffer);\n    context.forget_buffer(buffer);\n}\n\nconst CommandDesc delete_buffer_cmd = {\n    \"delete-buffer\",\n    \"db\",\n    \"delete-buffer [name]: delete current buffer or the buffer named <name> if given\",\n    single_optional_param,\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(menu(complete_buffer_name<false>)),\n    delete_buffer<false>\n};\n\nconst CommandDesc force_delete_buffer_cmd = {\n    \"delete-buffer!\",\n    \"db!\",\n    \"delete-buffer! [name]: delete current buffer or the buffer named <name> if \"\n    \"given, even if the buffer is unsaved\",\n    single_optional_param,\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(menu(complete_buffer_name<false>)),\n    delete_buffer<true>\n};\n\nconst CommandDesc rename_buffer_cmd = {\n    \"rename-buffer\",\n    nullptr,\n    \"rename-buffer <name>: change current buffer name\",\n    ParameterDesc{\n        {\n            { \"scratch\",  { {}, \"convert a file buffer to a scratch buffer\" } },\n            { \"file\",  { {}, \"convert a scratch buffer to a file buffer\" } }\n        },\n        ParameterDesc::Flags::None, 1, 1\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    filename_completer<false>,\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        if (parser.get_switch(\"scratch\") and parser.get_switch(\"file\"))\n            throw runtime_error(\"scratch and file are incompatible switches\");\n\n        auto& buffer = context.buffer();\n        if (parser.get_switch(\"scratch\"))\n            buffer.flags() &= ~(Buffer::Flags::File | Buffer::Flags::New);\n        if (parser.get_switch(\"file\"))\n            buffer.flags() |= Buffer::Flags::File;\n\n        const bool is_file = (buffer.flags() & Buffer::Flags::File);\n\n        if (not buffer.set_name(is_file ? parse_filename(parser[0]) : parser[0]))\n            throw runtime_error(format(\"unable to change buffer name to '{}': a buffer with this name already exists\", parser[0]));\n    }\n};\n\nstatic constexpr auto highlighter_scopes = { \"global/\", \"buffer/\", \"window/\", \"shared/\" };\n\ntemplate<bool add>\nCompletions highlighter_cmd_completer(\n    const Context& context, CommandParameters params,\n    size_t token_to_complete, ByteCount pos_in_token)\n{\n    if (token_to_complete == 0)\n    {\n\n        StringView path = params[0];\n        auto sep_it = find(path, '/');\n        if (sep_it == path.end())\n           return { 0_byte, pos_in_token, complete(path, pos_in_token, highlighter_scopes),\n                    Completions::Flags::Menu };\n\n        StringView scope{path.begin(), sep_it};\n        HighlighterGroup* root = nullptr;\n        if (scope == \"shared\")\n            root = &SharedHighlighters::instance();\n        else if (auto* s = get_scope_ifp(scope, context))\n            root = &s->highlighters().group();\n        else\n            return {};\n\n        auto offset = scope.length() + 1;\n        return offset_pos(root->complete_child(StringView{sep_it+1, path.end()}, pos_in_token - offset, add), offset);\n    }\n    else if (add and token_to_complete == 1)\n    {\n        StringView name = params[1];\n        return { 0_byte, name.length(), complete(name, pos_in_token, HighlighterRegistry::instance() | transform(&HighlighterRegistry::Item::key)),\n                 Completions::Flags::Menu };\n    }\n    else\n        return {};\n}\n\nstatic std::pair<HighlighterGroup&, Highlighter&> get_highlighter(const Context& context, StringView path)\n{\n    if (not path.empty() and path.back() == '/')\n        path = path.substr(0_byte, path.length() - 1);\n\n    auto sep_it = find(path, '/');\n    if (path.starts_with(\"buffer=\"))\n    {\n        auto& buffer_manager = BufferManager::instance();\n        while (sep_it != path.end() and not buffer_manager.get_buffer_ifp({path.begin()+7, sep_it}))\n            sep_it = std::find(sep_it+1, path.end(), '/');\n    }\n\n    StringView scope{path.begin(), sep_it};\n    auto* root = (scope == \"shared\") ? static_cast<HighlighterGroup*>(&SharedHighlighters::instance())\n                                     : static_cast<HighlighterGroup*>(&get_scope(scope, context).highlighters().group());\n    if (sep_it != path.end())\n        return {*root, root->get_child(StringView{sep_it+1, path.end()})};\n    return {*root, *root};\n}\n\nstatic void redraw_relevant_clients(Context& context, HighlighterGroup& root)\n{\n    for (auto&& client : ClientManager::instance())\n    {\n        if (&root == &SharedHighlighters::instance() or\n            &root == &GlobalScope::instance().highlighters().group() or\n            &root == &client->context().buffer().highlighters().group() or\n            &root == &client->context().window().highlighters().group())\n            client->force_redraw();\n    }\n}\n\nconst CommandDesc arrange_buffers_cmd = {\n    \"arrange-buffers\",\n    nullptr,\n    \"arrange-buffers <buffer>...: reorder the buffers in the buffers list\\n\"\n    \"    the named buffers will be moved to the front of the buffer list, in the order given\\n\"\n    \"    buffers that do not appear in the parameters will remain at the end of the list, keeping their current order\",\n    ParameterDesc{{}, ParameterDesc::Flags::None, 1},\n    CommandFlags::None,\n    CommandHelper{},\n    [](const Context& context, CommandParameters params, size_t, ByteCount cursor_pos)\n    {\n        return menu(complete_buffer_name<false>)(context, params.back(), cursor_pos);\n    },\n    [](const ParametersParser& parser, Context&, const ShellContext&)\n    {\n        BufferManager::instance().arrange_buffers(parser.positionals_from(0));\n    }\n};\n\nconst CommandDesc add_highlighter_cmd = {\n    \"add-highlighter\",\n    \"addhl\",\n    \"add-highlighter [-override] <path>/<name> <type> <type params>...: add a highlighter to the group identified by <path>\\n\"\n    \"    <path> is a '/' delimited path or the parent highlighter, starting with either\\n\"\n    \"   'global', 'buffer', 'window' or 'shared', if <name> is empty, it will be autogenerated\",\n    ParameterDesc{\n        { { \"override\", { {}, \"replace existing highlighter with same path if it exists\" } }, },\n        ParameterDesc::Flags::SwitchesOnlyAtStart, 2\n    },\n    CommandFlags::None,\n    [](const Context& context, CommandParameters params) -> String\n    {\n        if (params.size() > 1)\n        {\n            HighlighterRegistry& registry = HighlighterRegistry::instance();\n            auto it = registry.find(params[1]);\n            if (it != registry.end())\n            {\n                auto docstring = it->value.description->docstring;\n                auto desc_params = generate_switches_doc(it->value.description->params.switches);\n\n                if (desc_params.empty())\n                    return format(\"{}:\\n{}\", params[1], indent(docstring));\n                else\n                {\n                    auto desc_indent = Vector<String>{docstring, \"Switches:\", indent(desc_params)}\n                                           | transform([](auto& s) { return indent(s); });\n                    return format(\"{}:\\n{}\", params[1], join(desc_indent, \"\\n\"));\n                }\n            }\n        }\n        return \"\";\n    },\n    highlighter_cmd_completer<true>,\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        HighlighterRegistry& registry = HighlighterRegistry::instance();\n\n        auto begin = parser.begin();\n        StringView path = *begin++;\n        StringView type = *begin++;\n        Vector<String> highlighter_params;\n        for (; begin != parser.end(); ++begin)\n            highlighter_params.push_back(*begin);\n\n        auto it = registry.find(type);\n        if (it == registry.end())\n            throw runtime_error(format(\"no such highlighter type: '{}'\", type));\n\n        auto slash = find(path | reverse(), '/');\n        if (slash == path.rend())\n            throw runtime_error(\"no parent in path\");\n\n        auto auto_name = [](ConstArrayView<String> params) {\n            return join(params | transform([](StringView s) { return replace(s, \"/\", \"<slash>\"); }), \"_\");\n        };\n\n        String name{slash.base(), path.end()};\n        auto [root, parent] = get_highlighter(context, {path.begin(), slash.base() - 1});\n        parent.add_child(name.empty() ? auto_name(parser.positionals_from(1)) : std::move(name),\n                         it->value.factory(highlighter_params, &parent), (bool)parser.get_switch(\"override\"));\n\n        redraw_relevant_clients(context, root);\n    }\n};\n\nconst CommandDesc remove_highlighter_cmd = {\n    \"remove-highlighter\",\n    \"rmhl\",\n    \"remove-highlighter <path>: remove highlighter identified by <path>\",\n    single_param,\n    CommandFlags::None,\n    CommandHelper{},\n    highlighter_cmd_completer<false>,\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        StringView path = parser[0];\n        if (not path.empty() and path.back() == '/') // ignore trailing /\n            path = path.substr(0_byte, path.length() - 1_byte);\n\n        auto rev_path = path | reverse();\n        auto sep_it = find(rev_path, '/');\n        if (sep_it == rev_path.end())\n            return;\n        auto [root, parent] = get_highlighter(context, {path.begin(), sep_it.base()});\n        parent.remove_child({sep_it.base(), path.end()});\n        redraw_relevant_clients(context, root);\n    }\n};\n\nstatic Completions complete_hooks(const Context&, StringView prefix, ByteCount cursor_pos)\n{\n    return { 0_byte, cursor_pos, complete(prefix, cursor_pos, enum_desc(Meta::Type<Hook>{}) | transform(&EnumDesc<Hook>::name)) };\n}\n\nconst CommandDesc add_hook_cmd = {\n    \"hook\",\n    nullptr,\n    \"hook [<switches>] <scope> <hook_name> <filter> <command>: add <command> in <scope> \"\n    \"to be executed on hook <hook_name> when its parameter matches the <filter> regex\\n\"\n    \"<scope> can be:\\n\"\n    \"  * global: hook is executed for any buffer or window\\n\"\n    \"  * buffer: hook is executed only for the current buffer\\n\"\n    \"            (and any window for that buffer)\\n\"\n    \"  * window: hook is executed only for the current window\\n\",\n    ParameterDesc{\n        { { \"group\", { ArgCompleter{}, \"set hook group, see remove-hooks\" } },\n          { \"always\", { {}, \"run hook even if hooks are disabled\" } },\n          { \"once\", { {}, \"run the hook only once\" } } },\n        ParameterDesc::Flags::None, 4, 4\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(menu(complete_scope), menu(complete_hooks), complete_nothing, CommandManager::Completer{}),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        auto descs = enum_desc(Meta::Type<Hook>{});\n        auto it = find_if(descs, [&](const EnumDesc<Hook>& desc) { return desc.name == parser[1]; });\n        if (it == descs.end())\n            throw runtime_error{format(\"no such hook: '{}'\", parser[1])};\n\n        Regex regex{parser[2], RegexCompileFlags::Optimize};\n        const String& command = parser[3];\n        auto group = parser.get_switch(\"group\").value_or(StringView{});\n\n        if (any_of(group, [](char c) { return not is_word(c, { '-' }); }) or\n            (not group.empty() and not is_word(group[0])))\n            throw runtime_error{format(\"invalid group name '{}'\", group)};\n\n        const auto flags = (parser.get_switch(\"always\") ? HookFlags::Always : HookFlags::None) |\n                           (parser.get_switch(\"once\")   ? HookFlags::Once   : HookFlags::None);\n        get_scope(parser[0], context).hooks().add_hook(it->value, group.str(), flags,\n                                                       std::move(regex), command, context);\n    }\n};\n\nconst CommandDesc remove_hook_cmd = {\n    \"remove-hooks\",\n    \"rmhooks\",\n    \"remove-hooks <scope> <group>: remove all hooks whose group matches the regex <group>\",\n    double_params,\n    CommandFlags::None,\n    CommandHelper{},\n    [](const Context& context,\n       CommandParameters params, size_t token_to_complete,\n       ByteCount pos_in_token) -> Completions\n    {\n        if (token_to_complete == 0)\n            return menu(complete_scope)(context, params[0], pos_in_token);\n        else if (token_to_complete == 1)\n        {\n            if (auto scope = get_scope_ifp(params[0], context))\n                return { 0_byte, params[0].length(),\n                         scope->hooks().complete_hook_group(params[1], pos_in_token),\n                         Completions::Flags::Menu };\n        }\n        return {};\n    },\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        get_scope(parser[0], context).hooks().remove_hooks(Regex{parser[1]});\n    }\n};\n\nconst CommandDesc trigger_user_hook_cmd = {\n    \"trigger-user-hook\",\n    nullptr,\n    \"trigger-user-hook <param>: run 'User' hook with <param> as filter string\",\n    single_param,\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        context.hooks().run_hook(Hook::User, parser[0], context);\n    }\n};\n\nVector<String> params_to_shell(const ParametersParser& parser)\n{\n    Vector<String> vars;\n    for (size_t i = 0; i < parser.positional_count(); ++i)\n        vars.push_back(parser[i]);\n    return vars;\n}\n\nCompletions complete_completer_type(const Context&, StringView prefix, ByteCount cursor_pos)\n{\n   static constexpr StringView completers[] = {\"file\", \"client\", \"buffer\", \"shell-script\", \"shell-script-candidates\", \"command\", \"shell\"};\n   return { 0_byte, cursor_pos, complete(prefix, cursor_pos, completers) };\n}\n\n\nCommandCompleter make_command_completer(StringView type, StringView param, Completions::Flags completions_flags)\n{\n    if (type == \"file\")\n    {\n        return [=](const Context& context, CommandParameters params,\n                   size_t token_to_complete, ByteCount pos_in_token) {\n             const String& prefix = params[token_to_complete];\n             const auto& ignored_files = context.options()[\"ignored_files\"].get<Regex>();\n             return Completions{0_byte, pos_in_token,\n                                complete_filename(prefix, ignored_files,\n                                                  pos_in_token, FilenameFlags::Expand),\n                                completions_flags};\n        };\n    }\n    else if (type == \"client\")\n    {\n        return [=](const Context& context, CommandParameters params,\n                   size_t token_to_complete, ByteCount pos_in_token)\n        {\n             const String& prefix = params[token_to_complete];\n             auto& cm = ClientManager::instance();\n             return Completions{0_byte, pos_in_token,\n                                cm.complete_client_name(prefix, pos_in_token),\n                                completions_flags};\n        };\n    }\n    else if (type == \"buffer\")\n    {\n        return [=](const Context& context, CommandParameters params,\n                   size_t token_to_complete, ByteCount pos_in_token)\n        {\n             return add_flags(complete_buffer_name<false>, completions_flags)(\n                 context, params[token_to_complete], pos_in_token);\n        };\n    }\n    else if (type == \"shell-script\")\n    {\n        if (param.empty())\n            throw runtime_error(\"shell-script requires a shell script parameter\");\n\n        return ShellScriptCompleter{param.str(), completions_flags};\n    }\n    else if (type == \"shell-script-candidates\")\n    {\n        if (param.empty())\n            throw runtime_error(\"shell-script-candidates requires a shell script parameter\");\n\n        return ShellCandidatesCompleter{param.str(), completions_flags};\n    }\n    else if (type == \"command\")\n        return CommandManager::NestedCompleter{};\n    else if (type == \"shell\")\n    {\n        return [=](const Context& context, CommandParameters params,\n                   size_t token_to_complete, ByteCount pos_in_token)\n        {\n            return add_flags(shell_complete, completions_flags)(\n                context, params[token_to_complete], pos_in_token);\n        };\n    }\n    else\n        throw runtime_error(format(\"invalid command completion type '{}'\", type));\n}\n\nstatic CommandCompleter parse_completion_switch(const ParametersParser& parser, Completions::Flags completions_flags) {\n    for (StringView completion_switch : {\"file-completion\", \"client-completion\", \"buffer-completion\",\n                                         \"shell-script-completion\", \"shell-script-candidates\",\n                                         \"command-completion\", \"shell-completion\"})\n    {\n        if (auto param = parser.get_switch(completion_switch))\n        {\n            constexpr StringView suffix = \"-completion\";\n            if (completion_switch.ends_with(suffix))\n                completion_switch = completion_switch.substr(0, completion_switch.length() - suffix.length());\n            return make_command_completer(completion_switch, *param, completions_flags);\n        }\n    }\n    return {};\n}\n\nvoid define_command(const ParametersParser& parser, Context& context, const ShellContext&)\n{\n    const String& cmd_name = parser[0];\n    auto& cm = CommandManager::instance();\n\n    if (not all_of(cmd_name, is_identifier))\n        throw runtime_error(format(\"invalid command name: '{}'\", cmd_name));\n\n    if (cm.command_defined(cmd_name) and not parser.get_switch(\"override\"))\n        throw runtime_error(format(\"command '{}' already defined\", cmd_name));\n\n    CommandFlags flags = CommandFlags::None;\n    if (parser.get_switch(\"hidden\"))\n        flags = CommandFlags::Hidden;\n\n    const bool menu = (bool)parser.get_switch(\"menu\");\n    const Completions::Flags completions_flags = menu ?\n        Completions::Flags::Menu : Completions::Flags::None;\n\n    const String& commands = parser[1];\n    CommandFunc cmd;\n    ParameterDesc desc;\n    if (auto params = parser.get_switch(\"params\"))\n    {\n        size_t min = 0, max = -1;\n        StringView counts = *params;\n        static const Regex re{R\"((\\d+)?..(\\d+)?)\"};\n        MatchResults<const char*> res;\n        if (regex_match(counts.begin(), counts.end(), res, re))\n        {\n            if (res[1].matched)\n                min = (size_t)str_to_int({res[1].first, res[1].second});\n            if (res[2].matched)\n                max = (size_t)str_to_int({res[2].first, res[2].second});\n        }\n        else\n            min = max = (size_t)str_to_int(counts);\n\n        desc = ParameterDesc{ {}, ParameterDesc::Flags::SwitchesAsPositional, min, max };\n        cmd = [=](const ParametersParser& parser, Context& context, const ShellContext& sc) {\n            LocalScope local_scope{context};\n            CommandManager::instance().execute(commands, context,\n                                               { params_to_shell(parser), sc.env_vars });\n        };\n    }\n    else\n    {\n        desc = ParameterDesc{ {}, ParameterDesc::Flags::SwitchesAsPositional, 0, 0 };\n        cmd = [=](const ParametersParser& parser, Context& context, const ShellContext& sc) {\n            LocalScope local_scope{context};\n            CommandManager::instance().execute(commands, context, { {}, sc.env_vars });\n        };\n    }\n\n    CommandCompleter completer = parse_completion_switch(parser, completions_flags);\n    if (menu and not completer)\n        throw runtime_error(\"menu switch requires a completion switch\");\n    auto docstring = trim_indent(parser.get_switch(\"docstring\").value_or(StringView{}));\n\n    cm.register_command(cmd_name, cmd, docstring, desc, flags, CommandHelper{}, std::move(completer));\n}\n\nconst CommandDesc define_command_cmd = {\n    \"define-command\",\n    \"def\",\n    \"define-command [<switches>] <name> <cmds>: define a command <name> executing <cmds>\",\n    ParameterDesc{\n        { { \"params\",                   { ArgCompleter{},  \"take parameters, accessible to each shell escape as $0..$N\\n\"\n                                                 \"parameter should take the form <count> or <min>..<max> (both omittable)\" } },\n          { \"override\",                 { {}, \"allow overriding an existing command\" } },\n          { \"hidden\",                   { {}, \"do not display the command in completion candidates\" } },\n          { \"docstring\",                { ArgCompleter{},  \"define the documentation string for command\" } },\n          { \"menu\",                     { {}, \"treat completions as the only valid inputs\" } },\n          { \"file-completion\",          { {}, \"complete parameters using filename completion\" } },\n          { \"client-completion\",        { {}, \"complete parameters using client name completion\" } },\n          { \"buffer-completion\",        { {}, \"complete parameters using buffer name completion\" } },\n          { \"command-completion\",       { {}, \"complete parameters using kakoune command completion\" } },\n          { \"shell-completion\",         { {}, \"complete parameters using shell command completion\" } },\n          { \"shell-script-completion\",  { ArgCompleter{},  \"complete parameters using the given shell-script\" } },\n          { \"shell-script-candidates\",  { ArgCompleter{},  \"get the parameter candidates using the given shell-script\" } } },\n        ParameterDesc::Flags::None,\n        2, 2\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    define_command\n};\n\nstatic Completions complete_alias_name(const Context& context, StringView prefix, ByteCount cursor_pos)\n{\n   return { 0_byte, cursor_pos, complete(prefix, cursor_pos,\n                                           context.aliases().flatten_aliases()\n                                         | transform(&HashItem<String, String>::key))};\n}\n\nconst CommandDesc alias_cmd = {\n    \"alias\",\n    nullptr,\n    \"alias <scope> <alias> <command>: alias <alias> to <command> in <scope>\",\n    ParameterDesc{{}, ParameterDesc::Flags::None, 3, 3},\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(menu(complete_scope), complete_alias_name, complete_command_name),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        if (not CommandManager::instance().command_defined(parser[2]))\n            throw runtime_error(format(\"no such command: '{}'\", parser[2]));\n\n        AliasRegistry& aliases = get_scope(parser[0], context).aliases();\n        aliases.add_alias(parser[1], parser[2]);\n    }\n};\n\nconst CommandDesc unalias_cmd = {\n    \"unalias\",\n    nullptr,\n    \"unalias <scope> <alias> [<expected>]: remove <alias> from <scope>\\n\"\n    \"If <expected> is specified, remove <alias> only if its value is <expected>\",\n    ParameterDesc{{}, ParameterDesc::Flags::None, 2, 3},\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(menu(complete_scope), complete_alias_name, complete_command_name),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        AliasRegistry& aliases = get_scope(parser[0], context).aliases();\n        if (parser.positional_count() == 3 and\n            aliases[parser[1]] != parser[2])\n            return;\n        aliases.remove_alias(parser[1]);\n    }\n};\n\nconst CommandDesc complete_command_cmd = {\n    \"complete-command\",\n    \"compl\",\n    \"complete-command [<switches>] <name> <type> [<param>]\\n\"\n    \"define command completion\",\n    ParameterDesc{\n        { { \"menu\",                     { {}, \"treat completions as the only valid inputs\" } }, },\n        ParameterDesc::Flags::None, 2, 3},\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(complete_command_name, complete_completer_type),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        const Completions::Flags flags = parser.get_switch(\"menu\") ? Completions::Flags::Menu : Completions::Flags::None;\n        CommandCompleter completer = make_command_completer(parser[1], parser.positional_count() >= 3 ? parser[2] : StringView{}, flags);\n        CommandManager::instance().set_command_completer(parser[0], std::move(completer));\n    }\n};\n\nconst CommandDesc echo_cmd = {\n    \"echo\",\n    nullptr,\n    \"echo <params>...: display given parameters in the status line\",\n    ParameterDesc{\n        { { \"markup\", { {}, \"parse markup\" } },\n          { \"quoting\", { {arg_completer(Array{\"raw\", \"kakoune\", \"shell\"})}, \"quote each argument separately using the given style (raw|kakoune|shell)\" } },\n          { \"end-of-line\", { {}, \"add trailing end-of-line\" } },\n          { \"to-file\", { {filename_arg_completer<false>}, \"echo contents to given filename\" } },\n          { \"to-shell-script\", { ArgCompleter{}, \"pipe contents to given shell script\" } },\n          { \"debug\", { {}, \"write to debug buffer instead of status line\" } } },\n        ParameterDesc::Flags::SwitchesOnlyAtStart\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext& shell_context)\n    {\n        String message;\n        if (auto quoting = parser.get_switch(\"quoting\"))\n            message = join(parser | transform(quoter(option_from_string(Meta::Type<Quoting>{}, *quoting))),\n                           ' ', false);\n        else\n            message = join(parser, ' ', false);\n\n        if (parser.get_switch(\"end-of-line\"))\n            message.push_back('\\n');\n\n        if (auto filename = parser.get_switch(\"to-file\"))\n        {\n            BusyIndicator busy_indicator{context, [&](std::chrono::seconds elapsed) {\n                return DisplayLine{format(\"waiting while writing to '{}' ({}s)\", *filename, elapsed.count()),\n                                   context.faces()[\"Information\"]};\n            }};\n            write_to_file(*filename, message);\n        }\n        else if (auto command = parser.get_switch(\"to-shell-script\"))\n            ShellManager::instance().eval(*command, context, message, ShellManager::Flags::None, shell_context);\n        else if (parser.get_switch(\"debug\"))\n            write_to_debug_buffer(message);\n        else if (parser.get_switch(\"markup\"))\n            context.print_status(parse_display_line(message, context.faces()));\n        else\n            context.print_status({message, context.faces()[\"StatusLine\"]});\n    }\n};\n\nKeymapMode parse_keymap_mode(StringView str, const KeymapManager::UserModeList& user_modes)\n{\n    if (prefix_match(\"normal\", str)) return KeymapMode::Normal;\n    if (prefix_match(\"insert\", str)) return KeymapMode::Insert;\n    if (prefix_match(\"menu\", str))   return KeymapMode::Menu;\n    if (prefix_match(\"prompt\", str)) return KeymapMode::Prompt;\n    if (prefix_match(\"goto\", str))   return KeymapMode::Goto;\n    if (prefix_match(\"view\", str))   return KeymapMode::View;\n    if (prefix_match(\"user\", str))   return KeymapMode::User;\n    if (prefix_match(\"object\", str)) return KeymapMode::Object;\n\n    auto it = find(user_modes, str);\n    if (it == user_modes.end())\n        throw runtime_error(format(\"no such keymap mode: '{}'\", str));\n\n    char offset = static_cast<char>(KeymapMode::FirstUserMode);\n    return (KeymapMode)(std::distance(user_modes.begin(), it) + offset);\n}\n\nstatic constexpr auto modes = make_array<StringView>({ \"normal\", \"insert\", \"menu\", \"prompt\", \"goto\", \"view\", \"user\", \"object\" });\n\nconst CommandDesc debug_cmd = {\n    \"debug\",\n    nullptr,\n    \"debug <command>: write some debug information to the *debug* buffer\",\n    ParameterDesc{{}, ParameterDesc::Flags::SwitchesOnlyAtStart, 1},\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(\n        [](const Context& context, StringView prefix, ByteCount cursor_pos) -> Completions {\n               auto c = {\"info\", \"buffers\", \"options\", \"memory\", \"shared-strings\",\n                         \"profile-hash-maps\", \"faces\", \"mappings\", \"regex\", \"registers\"};\n               return { 0_byte, cursor_pos, complete(prefix, cursor_pos, c), Completions::Flags::Menu };\n    }),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        if (parser[0] == \"info\")\n        {\n            write_to_debug_buffer(format(\"version: {}\", version));\n            write_to_debug_buffer(format(\"pid: {}\", getpid()));\n            write_to_debug_buffer(format(\"session: {}\", Server::instance().session()));\n            #ifdef KAK_DEBUG\n            write_to_debug_buffer(\"build: debug\");\n            #else\n            write_to_debug_buffer(\"build: release\");\n            #endif\n        }\n        else if (parser[0] == \"buffers\")\n        {\n            write_to_debug_buffer(\"Buffers:\");\n            for (auto& buffer : BufferManager::instance())\n                write_to_debug_buffer(buffer->debug_description());\n        }\n        else if (parser[0] == \"options\")\n        {\n            write_to_debug_buffer(\"Options:\");\n            for (auto& option : context.options().flatten_options())\n                write_to_debug_buffer(format(\" * {} \\\"{}\\\"{}: {}\", option->name(), option->docstring(),\n                                             option->flags() & OptionFlags::Hidden ? \" (hidden)\" : \"\",\n                                             option->get_as_string(Quoting::Kakoune)));\n        }\n        else if (parser[0] == \"memory\")\n        {\n            size_t total = 0;\n            write_to_debug_buffer(\"Memory usage:\");\n            const ColumnCount column_size = 17;\n            write_to_debug_buffer(format(\"{:17} │{:17} │{:17} │{:17} \",\n                                         \"domain\",\n                                         \"bytes\",\n                                         \"active allocs\",\n                                         \"total allocs\"));\n            write_to_debug_buffer(format(\"{0}┼{0}┼{0}┼{0}\", String(Codepoint{0x2500}, column_size + 1)));\n\n            for (int domain = 0; domain < (int)MemoryDomain::Count; ++domain)\n            {\n                auto& stats = memory_stats[domain];\n                total += stats.allocated_bytes;\n                write_to_debug_buffer(format(\"{:17} │{:17} │{:17} │{:17} \",\n                                             domain_name((MemoryDomain)domain),\n                                             grouped(stats.allocated_bytes),\n                                             grouped(stats.allocation_count),\n                                             grouped(stats.total_allocation_count)));\n            }\n            write_to_debug_buffer({});\n            write_to_debug_buffer(format(\"  Total: {}\", grouped(total)));\n            #if defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 33))\n            write_to_debug_buffer(format(\"  Malloced: {}\", grouped(mallinfo2().uordblks)));\n            #elif defined(__GLIBC__) || defined(__CYGWIN__)\n            write_to_debug_buffer(format(\"  Malloced: {}\", grouped(mallinfo().uordblks)));\n            #endif\n        }\n        else if (parser[0] == \"shared-strings\")\n        {\n            StringRegistry::instance().debug_stats();\n        }\n        else if (parser[0] == \"profile-hash-maps\")\n        {\n            profile_hash_maps();\n        }\n        else if (parser[0] == \"faces\")\n        {\n            write_to_debug_buffer(\"Faces:\");\n            for (auto& face : context.faces().flatten_faces())\n                write_to_debug_buffer(format(\" * {}: {}\", face.key, face.value.face));\n        }\n        else if (parser[0] == \"mappings\")\n        {\n            auto& keymaps = context.keymaps();\n            auto user_modes = keymaps.user_modes();\n            write_to_debug_buffer(\"Mappings:\");\n            for (auto mode : concatenated(modes, user_modes))\n            {\n                KeymapMode m = parse_keymap_mode(mode, user_modes);\n                for (auto& key : keymaps.get_mapped_keys(m)) {\n                    KeyList kl = keymaps.get_mapping_keys(key, m);\n                    String mapping;\n                    for (const auto& k : kl)\n                        mapping += to_string(k);\n                    write_to_debug_buffer(format(\" * {} {}: '{}' {}\",\n                                          mode, key, mapping, keymaps.get_mapping_docstring(key, m)));\n                }\n            }\n        }\n        else if (parser[0] == \"regex\")\n        {\n            if (parser.positional_count() != 2)\n                throw runtime_error(\"expected a regex\");\n\n            write_to_debug_buffer(format(\" * {}:\\n{}\",\n                                  parser[1], dump_regex(compile_regex(parser[1], RegexCompileFlags::Optimize))));\n        }\n        else if (parser[0] == \"registers\")\n        {\n            write_to_debug_buffer(\"Register info:\");\n            for (auto&& [name, reg] : RegisterManager::instance())\n            {\n                auto content = reg->get(context);\n\n                if (content.size() == 1 and content[0] == \"\")\n                    continue;\n\n                write_to_debug_buffer(format(\" * {} = {}\\n\", name,\n                    join(content | transform(quote), \"\\n     = \")));\n            }\n        }\n        else\n            throw runtime_error(format(\"no such debug command: '{}'\", parser[0]));\n    }\n};\n\nconst CommandDesc source_cmd = {\n    \"source\",\n    nullptr,\n    \"source <filename> <params>...: execute commands contained in <filename>\\n\"\n    \"parameters are available in the sourced script as %arg{0}, %arg{1}, …\",\n    ParameterDesc{ {}, ParameterDesc::Flags::None, 1, (size_t)-1 },\n    CommandFlags::None,\n    CommandHelper{},\n    filename_completer<true>,\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        ProfileScope profile{context.options()[\"debug\"].get<DebugFlags>(), [&](std::chrono::microseconds duration) {\n            write_to_debug_buffer(format(\"sourcing '{}' took {} us\", parser[0], (size_t)duration.count()));\n        }};\n\n        String path = real_path(parse_filename(parser[0]));\n        MappedFile file_content{path};\n        try\n        {\n            auto params = parser | skip(1) | gather<Vector<String>>();\n            CommandManager::instance().execute(file_content, context,\n                                               {params, {{\"source\", path}}});\n        }\n        catch (Kakoune::runtime_error& err)\n        {\n            write_to_debug_buffer(format(\"{}:{}\", parser[0], err.what()));\n            throw;\n        }\n    }\n};\n\nstatic String option_doc_helper(const Context& context, CommandParameters params)\n{\n    const bool is_switch = params.size() > 1 and (params[0] == \"-add\" or params[0] == \"-remove\");\n    if (params.size() < 2 + (is_switch ? 1 : 0))\n        return \"\";\n\n    auto desc = GlobalScope::instance().option_registry().option_desc(params[1 + (is_switch ? 1 : 0)]);\n    if (not desc or desc->docstring().empty())\n        return \"\";\n\n    return format(\"{}:\\n{}\", desc->name(), indent(desc->docstring()));\n}\n\nstatic OptionManager& get_options(StringView scope, const Context& context, StringView option_name)\n{\n    if (scope == \"current\")\n        return context.options()[option_name].manager();\n    return get_scope(scope, context).options();\n}\n\nconst CommandDesc set_option_cmd = {\n    \"set-option\",\n    \"set\",\n    \"set-option [<switches>] <scope> <name> <value>: set option <name> in <scope> to <value>\\n\"\n    \"<scope> can be global, buffer, window, or current which refers to the narrowest \"\n    \"scope the option is set in\",\n    ParameterDesc{\n        { { \"add\",    { {}, \"add to option rather than replacing it\" } },\n          { \"remove\", { {}, \"remove from option rather than replacing it\" } } },\n        ParameterDesc::Flags::SwitchesOnlyAtStart, 2, (size_t)-1\n    },\n    CommandFlags::None,\n    option_doc_helper,\n    [](const Context& context, CommandParameters params,\n       size_t token_to_complete, ByteCount pos_in_token) -> Completions\n    {\n        if (token_to_complete == 0)\n            return menu(complete_scope_including_current)(context, params[0], pos_in_token);\n        else if (token_to_complete == 1)\n            return { 0_byte, params[1].length(),\n                     GlobalScope::instance().option_registry().complete_option_name(params[1], pos_in_token),\n                     Completions::Flags::Menu };\n        else if (token_to_complete == 2  and params[2].empty() and\n                 GlobalScope::instance().option_registry().option_exists(params[1]))\n        {\n            OptionManager& options = get_scope(params[0], context).options();\n            return {0_byte, params[2].length(),\n                    {options[params[1]].get_as_string(Quoting::Kakoune)},\n                    Completions::Flags::Quoted};\n        }\n        return Completions{};\n    },\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        bool add = (bool)parser.get_switch(\"add\");\n        bool remove = (bool)parser.get_switch(\"remove\");\n        if (add and remove)\n            throw runtime_error(\"cannot add and remove at the same time\");\n\n        Option& opt = get_options(parser[0], context, parser[1]).get_local_option(parser[1]);\n        if (add)\n            opt.add_from_strings(parser.positionals_from(2));\n        else if (remove)\n            opt.remove_from_strings(parser.positionals_from(2));\n        else\n            opt.set_from_strings(parser.positionals_from(2));\n    }\n};\n\nCompletions complete_option(const Context& context, CommandParameters params,\n                            size_t token_to_complete, ByteCount pos_in_token)\n{\n    if (token_to_complete == 0)\n        return menu(complete_scope_no_global)(context, params[0], pos_in_token);\n    else if (token_to_complete == 1)\n        return { 0_byte, params[1].length(),\n                 GlobalScope::instance().option_registry().complete_option_name(params[1], pos_in_token),\n                 Completions::Flags::Menu };\n    return Completions{};\n}\n\nconst CommandDesc unset_option_cmd = {\n    \"unset-option\",\n    \"unset\",\n    \"unset-option <scope> <name>: remove <name> option from scope, falling back on parent scope value\\n\"\n    \"<scope> can be buffer, window, or current which refers to the narrowest \"\n    \"scope the option is set in\",\n    double_params,\n    CommandFlags::None,\n    option_doc_helper,\n    complete_option,\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        auto& options = get_options(parser[0], context, parser[1]);\n        if (&options == &GlobalScope::instance().options())\n            throw runtime_error(\"cannot unset options in global scope\");\n        options.unset_option(parser[1]);\n    }\n};\n\nconst CommandDesc update_option_cmd = {\n    \"update-option\",\n    nullptr,\n    \"update-option <scope> <name>: update <name> option from scope\\n\"\n    \"some option types, such as line-specs or range-specs can be updated to latest buffer timestamp\\n\"\n    \"<scope> can be buffer, window, or current which refers to the narrowest \"\n    \"scope the option is set in\",\n    double_params,\n    CommandFlags::None,\n    option_doc_helper,\n    complete_option,\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        Option& opt = get_options(parser[0], context, parser[1]).get_local_option(parser[1]);\n        opt.update(context);\n    }\n};\n\nconst CommandDesc declare_option_cmd = {\n    \"declare-option\",\n    \"decl\",\n    \"declare-option [<switches>] <type> <name> [value]: declare option <name> of type <type>.\\n\"\n    \"set its initial value to <value> if given and the option did not exist\\n\"\n    \"Available types:\\n\"\n    \"    int: integer\\n\"\n    \"    bool: boolean (true/false or yes/no)\\n\"\n    \"    str: character string\\n\"\n    \"    regex: regular expression\\n\"\n    \"    int-list: list of integers\\n\"\n    \"    str-list: list of character strings\\n\"\n    \"    completions: list of completion candidates\\n\"\n    \"    line-specs: list of line specs\\n\"\n    \"    range-specs: list of range specs\\n\"\n    \"    str-to-str-map: map from strings to strings\\n\",\n    ParameterDesc{\n        { { \"hidden\",    { {}, \"do not display option name when completing\" } },\n          { \"docstring\", { ArgCompleter{},  \"specify option description\" } } },\n        ParameterDesc::Flags::SwitchesOnlyAtStart, 2, (size_t)-1\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(\n        [](const Context& context,\n           StringView prefix, ByteCount cursor_pos) -> Completions {\n               auto c = {\"int\", \"bool\", \"str\", \"regex\", \"int-list\", \"str-list\", \"completions\", \"line-specs\", \"range-specs\", \"str-to-str-map\"};\n               return { 0_byte, cursor_pos, complete(prefix, cursor_pos, c), Completions::Flags::Menu };\n    }),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        Option* opt = nullptr;\n\n        OptionFlags flags = OptionFlags::None;\n        if (parser.get_switch(\"hidden\"))\n            flags = OptionFlags::Hidden;\n\n        auto docstring = trim_indent(parser.get_switch(\"docstring\").value_or(StringView{}));\n        OptionsRegistry& reg = GlobalScope::instance().option_registry();\n\n\n        if (parser[0] == \"int\")\n            opt = &reg.declare_option<int>(parser[1], docstring, 0, flags);\n        else if (parser[0] == \"bool\")\n            opt = &reg.declare_option<bool>(parser[1], docstring, false, flags);\n        else if (parser[0] == \"str\")\n            opt = &reg.declare_option<String>(parser[1], docstring, \"\", flags);\n        else if (parser[0] == \"regex\")\n            opt = &reg.declare_option<Regex>(parser[1], docstring, Regex{}, flags);\n        else if (parser[0] == \"int-list\")\n            opt = &reg.declare_option<Vector<int, MemoryDomain::Options>>(parser[1], docstring, {}, flags);\n        else if (parser[0] == \"str-list\")\n            opt = &reg.declare_option<Vector<String, MemoryDomain::Options>>(parser[1], docstring, {}, flags);\n        else if (parser[0] == \"completions\")\n            opt = &reg.declare_option<CompletionList>(parser[1], docstring, {}, flags);\n        else if (parser[0] == \"line-specs\")\n            opt = &reg.declare_option<TimestampedList<LineAndSpec>>(parser[1], docstring, {}, flags);\n        else if (parser[0] == \"range-specs\")\n            opt = &reg.declare_option<TimestampedList<RangeAndString>>(parser[1], docstring, {}, flags);\n        else if (parser[0] == \"str-to-str-map\")\n            opt = &reg.declare_option<HashMap<String, String, MemoryDomain::Options>>(parser[1], docstring, {}, flags);\n        else\n            throw runtime_error(format(\"no such option type: '{}'\", parser[0]));\n\n        if (parser.positional_count() > 2)\n            opt->set_from_strings(parser.positionals_from(2));\n    }\n};\n\ntemplate<bool unmap>\nstatic Completions map_key_completer(const Context& context, CommandParameters params,\n                                     size_t token_to_complete, ByteCount pos_in_token)\n{\n    if (token_to_complete == 0)\n        return menu(complete_scope)(context, params[0], pos_in_token);\n    if (token_to_complete == 1)\n    {\n        auto& user_modes = get_scope(params[0], context).keymaps().user_modes();\n        return { 0_byte, params[1].length(),\n                 complete(params[1], pos_in_token, concatenated(modes, user_modes)),\n                 Completions::Flags::Menu };\n    }\n    if (unmap and token_to_complete == 2)\n    {\n        KeymapManager& keymaps = get_scope(params[0], context).keymaps();\n        KeymapMode keymap_mode = parse_keymap_mode(params[1], keymaps.user_modes());\n        KeyList keys = keymaps.get_mapped_keys(keymap_mode);\n\n        return { 0_byte, params[2].length(),\n                 complete(params[2], pos_in_token,\n                          keys | transform([](Key k) { return to_string(k); })\n                               | gather<Vector<String>>()),\n                 Completions::Flags::Menu };\n    }\n    return {};\n}\n\nconst CommandDesc map_key_cmd = {\n    \"map\",\n    nullptr,\n    \"map [<switches>] <scope> <mode> <key> <keys>: map <key> to <keys> in given <mode> in <scope>\",\n    ParameterDesc{\n        { { \"docstring\", { ArgCompleter{},  \"specify mapping description\" } } },\n        ParameterDesc::Flags::None, 4, 4\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    map_key_completer<false>,\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        KeymapManager& keymaps = get_scope(parser[0], context).keymaps();\n        KeymapMode keymap_mode = parse_keymap_mode(parser[1], keymaps.user_modes());\n\n        KeyList key = parse_keys(parser[2]);\n        if (key.size() != 1)\n            throw runtime_error(\"only a single key can be mapped\");\n\n        KeyList mapping = parse_keys(parser[3]);\n        keymaps.map_key(key[0], keymap_mode, std::move(mapping),\n                        trim_indent(parser.get_switch(\"docstring\").value_or(\"\")));\n    }\n};\n\nconst CommandDesc unmap_key_cmd = {\n    \"unmap\",\n    nullptr,\n    \"unmap <scope> <mode> [<key> [<expected-keys>]]: unmap <key> from given <mode> in <scope>.\\n\"\n    \"If <expected-keys> is specified, remove the mapping only if its value is <expected-keys>.\\n\"\n    \"If only <scope> and <mode> are specified remove all mappings\",\n    ParameterDesc{{}, ParameterDesc::Flags::None, 2, 4},\n    CommandFlags::None,\n    CommandHelper{},\n    map_key_completer<true>,\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        KeymapManager& keymaps = get_scope(parser[0], context).keymaps();\n        KeymapMode keymap_mode = parse_keymap_mode(parser[1], keymaps.user_modes());\n\n        if (parser.positional_count() == 2)\n        {\n            keymaps.unmap_keys(keymap_mode);\n            return;\n        }\n\n        KeyList key = parse_keys(parser[2]);\n        if (key.size() != 1)\n            throw runtime_error(\"only a single key can be unmapped\");\n\n        if (keymaps.is_mapped(key[0], keymap_mode) and\n            (parser.positional_count() < 4 or\n             keymaps.get_mapping_keys(key[0], keymap_mode) == parse_keys(parser[3])))\n            keymaps.unmap_key(key[0], keymap_mode);\n    }\n};\n\ntemplate<size_t... P>\nParameterDesc make_context_wrap_params_impl(Array<HashItem<String, SwitchDesc>, sizeof...(P)>&& additional_params,\n                                            std::index_sequence<P...>)\n{\n    return { { { \"client\",     { {client_arg_completer},  \"run in the client context for each client in the given comma separatd list\" } },\n               { \"try-client\", { {client_arg_completer},  \"run in given client context if it exists, or else in the current one\" } },\n               { \"buffer\",     { {complete_buffer_name<false>},  \"run in a disposable context for each given buffer in the comma separated list argument\" } },\n               { \"draft\",      { {}, \"run in a disposable context\" } },\n               { \"itersel\",    { {}, \"run once for each selection with that selection as the only one\" } },\n               std::move(additional_params[P])...},\n        ParameterDesc::Flags::SwitchesOnlyAtStart, 1\n    };\n}\n\ntemplate<size_t N>\nParameterDesc make_context_wrap_params(Array<HashItem<String, SwitchDesc>, N>&& additional_params)\n{\n    return make_context_wrap_params_impl(std::move(additional_params), std::make_index_sequence<N>());\n}\n\ntemplate<typename Func>\nvoid context_wrap(const ParametersParser& parser, Context& context, StringView default_saved_regs, Func func)\n{\n    if ((int)(bool)parser.get_switch(\"buffer\") +\n        (int)(bool)parser.get_switch(\"client\") +\n        (int)(bool)parser.get_switch(\"try-client\") > 1)\n        throw runtime_error{\"only one of -buffer, -client or -try-client can be specified\"};\n\n    const auto& register_manager = RegisterManager::instance();\n    auto make_register_restorer = [&](char c) {\n        auto& reg = register_manager[c];\n        return OnScopeEnd([&, c, save=reg.save(context), d=ScopedSetBool{reg.modified_hook_disabled()}] {\n            try\n            {\n                reg.restore(context, save);\n            }\n            catch (runtime_error& err)\n            {\n                write_to_debug_buffer(format(\"failed to restore register '{}': {}\", c, err.what()));\n            }\n        });\n    };\n    Vector<decltype(make_register_restorer(0))> saved_registers;\n    for (auto c : parser.get_switch(\"save-regs\").value_or(default_saved_regs))\n        saved_registers.push_back(make_register_restorer(c));\n\n    if (auto bufnames = parser.get_switch(\"buffer\"))\n    {\n        auto context_wrap_for_buffer = [&](Buffer& buffer) {\n            InputHandler input_handler{{buffer, Selection{}}, Context::Flags::Draft};\n            func(parser, input_handler.context());\n        };\n        if (*bufnames == \"*\")\n        {\n            for (auto&& buffer : BufferManager::instance()\n                               | transform(&UniquePtr<Buffer>::get)\n                               | filter([](Buffer* buf) { return not (buf->flags() & Buffer::Flags::Debug); })\n                               | gather<Vector<SafePtr<Buffer>>>()) // gather as we might be mutating the buffer list in the loop.\n                context_wrap_for_buffer(*buffer);\n        }\n        else\n            for (auto&& name : *bufnames\n                             | split<StringView>(',', '\\\\')\n                             | transform(unescape<',', '\\\\'>))\n                context_wrap_for_buffer(BufferManager::instance().get_buffer(name));\n        return;\n    }\n\n    auto context_wrap_for_context = [&parser, &func](Context& base_context) {\n        Optional<InputHandler> input_handler;\n\n        const bool draft = (bool)parser.get_switch(\"draft\");\n        if (draft)\n        {\n            input_handler.emplace(base_context.selections(),\n                                  Context::Flags::Draft,\n                                  base_context.name());\n\n            // Preserve window so that window scope is available\n            if (base_context.has_window())\n                input_handler->context().set_window(base_context.window());\n\n            // We do not want this draft context to commit undo groups if the real one is\n            // going to commit the whole thing later\n            if (base_context.is_editing())\n                input_handler->context().disable_undo_handling();\n        }\n\n        Context& c = input_handler ? input_handler->context() : base_context;\n\n        ScopedEdition edition{c};\n        ScopedSelectionEdition selection_edition{c};\n\n        if (parser.get_switch(\"itersel\"))\n        {\n            SelectionList sels{base_context.selections()};\n            Vector<Selection> new_sels;\n            size_t main = 0;\n            size_t timestamp = c.buffer().timestamp();\n            bool one_selection_succeeded = false;\n            for (auto& sel : sels)\n            {\n                c.selections_write_only() = SelectionList{sels.buffer(), sel, sels.timestamp()};\n                c.selections().update();\n\n                try\n                {\n                    func(parser, c);\n                    one_selection_succeeded = true;\n\n                    if (&sels.buffer() != &c.buffer())\n                        throw runtime_error(\"buffer has changed while iterating on selections\");\n\n                    if (not draft)\n                    {\n                        update_selections(new_sels, main, c.buffer(), timestamp);\n                        timestamp = c.buffer().timestamp();\n                        if (&sel == &sels.main())\n                            main = new_sels.size() + c.selections().main_index();\n\n                        const auto middle = new_sels.insert(new_sels.end(), c.selections().begin(), c.selections().end());\n                        std::inplace_merge(new_sels.begin(), middle, new_sels.end(), compare_selections);\n                    }\n                }\n                catch (no_selections_remaining&) {}\n            }\n\n            if (not one_selection_succeeded)\n            {\n                c.selections_write_only() = std::move(sels);\n                throw no_selections_remaining{};\n            }\n\n            if (not draft)\n                c.selections_write_only().set(std::move(new_sels), main);\n        }\n        else\n        {\n            const bool collapse_jumps = not (c.flags() & Context::Flags::Draft) and c.has_buffer();\n            auto& jump_list = c.jump_list();\n            const size_t prev_index = jump_list.current_index();\n            auto jump = collapse_jumps ? c.selections() : Optional<SelectionList>{};\n\n            func(parser, c);\n\n            // If the jump list got mutated, collapse all jumps into a single one from original selections\n            if (auto index = jump_list.current_index();\n                collapse_jumps and index > prev_index and\n                contains(BufferManager::instance(), &jump->buffer()))\n                jump_list.push(std::move(*jump), prev_index);\n        }\n    };\n\n    ClientManager& cm = ClientManager::instance();\n    if (auto client_names = parser.get_switch(\"client\"))\n    {\n        if (*client_names == \"*\")\n        {\n            for (auto&& client : ClientManager::instance()\n                               | transform(&UniquePtr<Client>::get)\n                               | gather<Vector<SafePtr<Client>>>()) // gather as we might be mutating the client list in the loop.\n                context_wrap_for_context(client->context());\n        }\n        else\n            for (auto&& name : *client_names\n                             | split<StringView>(',', '\\\\')\n                             | transform(unescape<',', '\\\\'>))\n                context_wrap_for_context(ClientManager::instance().get_client(name).context());\n    }\n    else if (auto client_name = parser.get_switch(\"try-client\"))\n    {\n        Client* client = cm.get_client_ifp(*client_name);\n        context_wrap_for_context(client ? client->context() : context);\n    }\n    else\n        context_wrap_for_context(context);\n}\n\nconst CommandDesc execute_keys_cmd = {\n    \"execute-keys\",\n    \"exec\",\n    \"execute-keys [<switches>] <keys>: execute given keys as if entered by user\",\n    make_context_wrap_params<3>({{\n        {\"save-regs\",  {ArgCompleter{}, \"restore all given registers after execution (default: '/\\\"|^@:')\"}},\n        {\"with-maps\",  {{}, \"use user defined key mapping when executing keys\"}},\n        {\"with-hooks\", {{}, \"trigger hooks while executing keys\"}}\n    }}),\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        context_wrap(parser, context, \"/\\\"|^@:\", [](const ParametersParser& parser, Context& context) {\n            ScopedSetBool disable_keymaps(context.keymaps_disabled(), not parser.get_switch(\"with-maps\"));\n            ScopedSetBool disable_hooks(context.hooks_disabled(), not parser.get_switch(\"with-hooks\"));\n\n            for (auto& key : parser | transform(parse_keys) | flatten())\n                context.input_handler().handle_key(key);\n        });\n    }\n};\n\n\nconst CommandDesc evaluate_commands_cmd = {\n    \"evaluate-commands\",\n    \"eval\",\n    \"evaluate-commands [<switches>] <commands>...: execute commands as if entered by user\",\n    make_context_wrap_params<3>({{\n        {\"save-regs\",  {ArgCompleter{}, \"restore all given registers after execution (default: '')\"}},\n        {\"no-hooks\", { {}, \"disable hooks while executing commands\" }},\n        {\"verbatim\", { {}, \"do not reparse argument\" }}\n    }}),\n    CommandFlags::None,\n    CommandHelper{},\n    CommandManager::NestedCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext& shell_context)\n    {\n        context_wrap(parser, context, {}, [&](const ParametersParser& parser, Context& context) {\n            const bool no_hooks = context.hooks_disabled() or parser.get_switch(\"no-hooks\");\n            ScopedSetBool disable_hooks(context.hooks_disabled(), no_hooks);\n\n            LocalScope local_scope{context};\n            if (parser.get_switch(\"verbatim\"))\n                CommandManager::instance().execute_single_command(parser | gather<Vector<String>>(), context, shell_context);\n            else\n                CommandManager::instance().execute(join(parser, ' ', false), context, shell_context);\n        });\n    }\n};\n\nstruct CapturedShellContext\n{\n    explicit CapturedShellContext(const ShellContext& sc)\n      : params{sc.params.begin(), sc.params.end()}, env_vars{sc.env_vars} {}\n\n    Vector<String> params;\n    EnvVarMap env_vars;\n\n    operator ShellContext() const { return { params, env_vars }; }\n};\n\nconst CommandDesc prompt_cmd = {\n    \"prompt\",\n    nullptr,\n    \"prompt [<switches>] <prompt> <command>: prompt the user to enter a text string \"\n    \"and then executes <command>, entered text is available in the 'text' value\",\n    ParameterDesc{\n        { { \"init\", { ArgCompleter{}, \"set initial prompt content\" } },\n          { \"password\", { {}, \"Do not display entered text and clear reg after command\" } },\n          { \"menu\", { {}, \"treat completions as the only valid inputs\" } },\n          { \"file-completion\", { {}, \"use file completion for prompt\" } },\n          { \"client-completion\", { {}, \"use client completion for prompt\" } },\n          { \"buffer-completion\", { {}, \"use buffer completion for prompt\" } },\n          { \"command-completion\", { {}, \"use command completion for prompt\" } },\n          { \"shell-completion\", { {}, \"use shell command completion for prompt\" } },\n          { \"shell-script-completion\", { ArgCompleter{}, \"use shell command completion for prompt\" } },\n          { \"shell-script-candidates\", { ArgCompleter{}, \"use shell command completion for prompt\" } },\n          { \"on-change\", { ArgCompleter{}, \"command to execute whenever the prompt changes\" } },\n          { \"on-abort\", { ArgCompleter{}, \"command to execute whenever the prompt is canceled\" } } },\n        ParameterDesc::Flags::None, 2, 2\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext& shell_context)\n    {\n        const String& command = parser[1];\n        auto initstr = parser.get_switch(\"init\").value_or(StringView{});\n\n        const Completions::Flags completions_flags = parser.get_switch(\"menu\") ?\n            Completions::Flags::Menu : Completions::Flags::None;\n        PromptCompleterAdapter completer = parse_completion_switch(parser, completions_flags);\n\n        const auto flags = parser.get_switch(\"password\") ?\n            PromptFlags::Password : PromptFlags::None;\n\n        context.input_handler().prompt(\n            parser[0], initstr.str(), {}, context.faces()[\"Prompt\"],\n            flags, '_', std::move(completer),\n            [command,\n             on_change = parser.get_switch(\"on-change\").value_or(\"\").str(),\n             on_abort = parser.get_switch(\"on-abort\").value_or(\"\").str(),\n             sc = CapturedShellContext{shell_context}]\n            (StringView str, PromptEvent event, Context& context) mutable\n            {\n                if ((event == PromptEvent::Abort and on_abort.empty()) or\n                    (event == PromptEvent::Change and on_change.empty()))\n                    return;\n\n                sc.env_vars[\"text\"_sv] = String{String::NoCopy{}, str};\n                auto remove_text = OnScopeEnd([&] {\n                    sc.env_vars.erase(\"text\"_sv);\n                });\n\n                StringView cmd;\n                switch (event)\n                {\n                    case PromptEvent::Validate: cmd = command; break;\n                    case PromptEvent::Change: cmd = on_change; break;\n                    case PromptEvent::Abort: cmd = on_abort; break;\n                }\n                try\n                {\n                    CommandManager::instance().execute(cmd, context, sc);\n                }\n                catch (Kakoune::runtime_error& error)\n                {\n                    context.print_status({error.what().str(), context.faces()[\"Error\"]});\n                    context.hooks().run_hook(Hook::RuntimeError, error.what(), context);\n                }\n            });\n    }\n};\n\nconst CommandDesc on_key_cmd = {\n    \"on-key\",\n    nullptr,\n    \"on-key [<switches>] <command>: wait for next user key and then execute <command>, \"\n    \"with key available in the `key` value\",\n    ParameterDesc{\n        { { \"mode-name\", { ArgCompleter{}, \"set mode name to use\" } } },\n        ParameterDesc::Flags::None, 1, 1\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext& shell_context)\n    {\n        String command = parser[0];\n\n        CapturedShellContext sc{shell_context};\n        context.input_handler().on_next_key(\n            parser.get_switch(\"mode-name\").value_or(\"on-key\"),\n            KeymapMode::None, [=](Key key, Context& context) mutable {\n            sc.env_vars[\"key\"_sv] = to_string(key);\n\n            CommandManager::instance().execute(command, context, sc);\n        });\n    }\n};\n\nconst CommandDesc info_cmd = {\n    \"info\",\n    nullptr,\n    \"info [<switches>] <text>: display an info box containing <text>\",\n    ParameterDesc{\n        { { \"anchor\", { ArgCompleter{}, \"set info anchoring <line>.<column>\" } },\n          { \"style\", { {arg_completer(Array{\"above\", \"below\", \"menu\", \"modal\"})}, \"set info style (above, below, menu, modal)\" } },\n          { \"markup\", { {}, \"parse markup\" } },\n          { \"title\", { ArgCompleter{}, \"set info title\" } } },\n        ParameterDesc::Flags::None, 0, 1\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        if (not context.has_client())\n            return;\n\n        const InfoStyle style = parser.get_switch(\"style\").map(\n            [](StringView style) -> Optional<InfoStyle> {\n                if (style == \"above\") return InfoStyle::InlineAbove;\n                if (style == \"below\") return InfoStyle::InlineBelow;\n                if (style == \"menu\") return InfoStyle::MenuDoc;\n                if (style == \"modal\") return InfoStyle::Modal;\n                throw runtime_error(format(\"invalid style: '{}'\", style));\n            }).value_or(parser.get_switch(\"anchor\") ? InfoStyle::Inline : InfoStyle::Prompt);\n\n        context.client().info_hide(style == InfoStyle::Modal);\n        if (parser.positional_count() == 0)\n            return;\n\n        const BufferCoord pos = parser.get_switch(\"anchor\").map(\n            [](StringView anchor) {\n                auto dot = find(anchor, '.');\n                if (dot == anchor.end())\n                    throw runtime_error(\"expected <line>.<column> for anchor\");\n\n                return BufferCoord{str_to_int({anchor.begin(), dot})-1,\n                                   str_to_int({dot+1, anchor.end()})-1};\n            }).value_or(BufferCoord{});\n\n        auto title = parser.get_switch(\"title\").value_or(StringView{});\n        if (parser.get_switch(\"markup\"))\n            context.client().info_show(parse_display_line(title, context.faces()),\n                                       parse_display_line_list(parser[0], context.faces()),\n                                       pos, style);\n        else\n            context.client().info_show(title.str(), parser[0], pos, style);\n    }\n};\n\nconst CommandDesc try_catch_cmd = {\n    \"try\",\n    nullptr,\n    \"try <cmds> [catch <error_cmds>]...: execute <cmds> in current context.\\n\"\n    \"if an error is raised and <error_cmds> is specified, execute it and do\\n\"\n    \"not propagate that error. If <error_cmds> raises an error and another\\n\"\n    \"<error_cmds> is provided, execute this one and so-on\\n\",\n    ParameterDesc{{}, ParameterDesc::Flags::None, 1},\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext& shell_context)\n    {\n        if ((parser.positional_count() % 2) != 1)\n            throw wrong_argument_count();\n\n        for (size_t i = 1; i < parser.positional_count(); i += 2)\n        {\n            if (parser[i] != \"catch\")\n                throw runtime_error(\"usage: try <commands> [catch <on error commands>]...\");\n        }\n\n        CommandManager& command_manager = CommandManager::instance();\n        Optional<ShellContext> shell_context_with_error;\n        for (size_t i = 0; i < parser.positional_count(); i += 2)\n        {\n            if (i == 0 or i < parser.positional_count() - 1)\n            {\n                try\n                {\n                    command_manager.execute(parser[i], context,\n                                            shell_context_with_error.value_or(shell_context));\n                    return;\n                }\n                catch (const runtime_error& error)\n                {\n                    shell_context_with_error.emplace(shell_context);\n                    shell_context_with_error->env_vars[StringView{\"error\"}] = error.what().str();\n                }\n            }\n            else\n                command_manager.execute(parser[i], context,\n                                        shell_context_with_error.value_or(shell_context));\n        }\n    }\n};\n\nstatic Completions complete_face(const Context& context,\n                                 StringView prefix, ByteCount cursor_pos)\n{\n    return {0_byte, cursor_pos,\n            complete(prefix, cursor_pos, context.faces().flatten_faces() |\n                     transform([](auto& entry) -> const String& { return entry.key; }))};\n}\n\nstatic String face_doc_helper(const Context& context, CommandParameters params)\n{\n    if (params.size() < 2)\n        return {};\n    try\n    {\n        auto face = context.faces()[params[1]];\n        return format(\"{}:\\n{}\", params[1], indent(to_string(face)));\n    }\n    catch (runtime_error&)\n    {\n        return {};\n    }\n}\n\nconst CommandDesc set_face_cmd = {\n    \"set-face\",\n    \"face\",\n    \"set-face <scope> <name> <facespec>: set face <name> to <facespec> in <scope>\\n\"\n    \"\\n\"\n    \"facespec format is:\\n\"\n    \"    <fg color>[,<bg color>[,<underline color>]][+<attributes>][@<base>]\\n\"\n    \"colors are either a color name, rgb:######, or rgba:######## values.\\n\"\n    \"attributes is a combination of:\\n\"\n    \"    u: underline, c: curly underline, U: double underline,\\n\"\n    \"    i: italic,            b: bold,            r: reverse,\\n\"\n    \"    s: strikethrough,     B: blink,           d: dim,\\n\"\n    \"    f: final foreground,              g: final background,\\n\"\n    \"    a: final attributes,              F: same as +fga\\n\"\n    \"facespec can as well just be the name of another face.\\n\"\n    \"if a base face is specified, colors and attributes are applied on top of it\",\n    ParameterDesc{{}, ParameterDesc::Flags::None, 3, 3},\n    CommandFlags::None,\n    face_doc_helper,\n    make_completer(menu(complete_scope), complete_face, complete_face),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        get_scope(parser[0], context).faces().add_face(parser[1], parser[2], true);\n\n        for (auto& client : ClientManager::instance())\n            client->force_redraw();\n    }\n};\n\nconst CommandDesc unset_face_cmd = {\n    \"unset-face\",\n    nullptr,\n    \"unset-face <scope> <name>: remove <face> from <scope>\",\n    double_params,\n    CommandFlags::None,\n    face_doc_helper,\n    make_completer(menu(complete_scope), menu(complete_face)),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n       get_scope(parser[0], context).faces().remove_face(parser[1]);\n    }\n};\n\nconst CommandDesc rename_client_cmd = {\n    \"rename-client\",\n    nullptr,\n    \"rename-client <name>: set current client name to <name>\",\n    single_param,\n    CommandFlags::None,\n    CommandHelper{},\n    make_single_word_completer([](const Context& context){ return context.name(); }),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        const String& name = parser[0];\n        if (not all_of(name, is_identifier))\n            throw runtime_error{format(\"invalid client name: '{}'\", name)};\n        else if (ClientManager::instance().client_name_exists(name) and\n                 context.name() != name)\n            throw runtime_error{format(\"client name '{}' is not unique\", name)};\n        else\n            context.set_name(name);\n    }\n};\n\nconst CommandDesc set_register_cmd = {\n    \"set-register\",\n    \"reg\",\n    \"set-register <name> <values>...: set register <name> to <values>\",\n    ParameterDesc{{}, ParameterDesc::Flags::SwitchesAsPositional, 1},\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(\n         [](const Context& context,\n            StringView prefix, ByteCount cursor_pos) -> Completions {\n             return { 0_byte, cursor_pos,\n                      RegisterManager::instance().complete_register_name(prefix, cursor_pos) };\n        }),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        RegisterManager::instance()[parser[0]].set(context, parser.positionals_from(1));\n    }\n};\n\nconst CommandDesc select_cmd = {\n    \"select\",\n    nullptr,\n    \"select <selection_desc>...: select given selections\\n\"\n    \"\\n\"\n    \"selection_desc format is <anchor_line>.<anchor_column>,<cursor_line>.<cursor_column>\",\n    ParameterDesc{{\n            {\"timestamp\", {ArgCompleter{}, \"specify buffer timestamp at which those selections are valid\"}},\n            {\"codepoint\", {{}, \"columns are specified in codepoints, not bytes\"}},\n            {\"display-column\", {{}, \"columns are specified in display columns, not bytes\"}}\n        },\n        ParameterDesc::Flags::SwitchesOnlyAtStart, 1\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        auto& buffer = context.buffer();\n        const size_t timestamp = parser.get_switch(\"timestamp\").map(str_to_int_ifp).cast<size_t>().value_or(buffer.timestamp());\n        ColumnType column_type = ColumnType::Byte;\n        if (parser.get_switch(\"codepoint\"))\n            column_type = ColumnType::Codepoint;\n        else if (parser.get_switch(\"display-column\"))\n            column_type = ColumnType::DisplayColumn;\n        ColumnCount tabstop = context.options()[\"tabstop\"].get<int>();\n        ScopedSelectionEdition selection_edition{context};\n        context.selections_write_only() = selection_list_from_strings(buffer, column_type, parser.positionals_from(0), timestamp, 0, tabstop);\n    }\n};\n\nconst CommandDesc change_directory_cmd = {\n    \"change-directory\",\n    \"cd\",\n    \"change-directory [<directory>]: change the server's working directory to <directory>, or the home directory if unspecified\",\n    single_optional_param,\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(\n         [](const Context& context,\n            StringView prefix, ByteCount cursor_pos) -> Completions {\n             return { 0_byte, cursor_pos,\n                      complete_filename(prefix,\n                                        context.options()[\"ignored_files\"].get<Regex>(),\n                                        cursor_pos, FilenameFlags::OnlyDirectories),\n                      Completions::Flags::Menu };\n        }),\n    [](const ParametersParser& parser, Context& ctx, const ShellContext&)\n    {\n        StringView target = parser.positional_count() == 1 ? StringView{parser[0]} : \"~\";\n        auto path = real_path(parse_filename(target));\n        if (chdir(path.c_str()) != 0)\n            throw runtime_error(format(\"unable to change to directory: '{}'\", target));\n        for (auto& buffer : BufferManager::instance())\n            buffer->update_display_name();\n        ctx.hooks().run_hook(Hook::EnterDirectory, path, ctx);\n    }\n};\n\nconst CommandDesc rename_session_cmd = {\n    \"rename-session\",\n    nullptr,\n    \"rename-session <name>: change remote session name\",\n    single_param,\n    CommandFlags::None,\n    CommandHelper{},\n    make_single_word_completer([](const Context&){ return Server::instance().session(); }),\n    [](const ParametersParser& parser, Context& ctx, const ShellContext&)\n    {\n        String old_name = Server::instance().session();\n        if (not Server::instance().rename_session(parser[0]))\n            throw runtime_error(format(\"unable to rename current session: '{}' may be already in use\", parser[0]));\n        ctx.hooks().run_hook(Hook::SessionRenamed, format(\"{}:{}\", old_name, Server::instance().session()), ctx);\n    }\n};\n\nconst CommandDesc fail_cmd = {\n    \"fail\",\n    nullptr,\n    \"fail [<message>]: raise an error with the given message\",\n    ParameterDesc{},\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context&, const ShellContext&)\n    {\n        throw failure{join(parser, \" \")};\n    }\n};\n\nconst CommandDesc declare_user_mode_cmd = {\n    \"declare-user-mode\",\n    nullptr,\n    \"declare-user-mode <name>: add a new user keymap mode\",\n    single_param,\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        context.keymaps().add_user_mode(parser[0]);\n    }\n};\n\n// We need ownership of the mode_name in the lock case\nvoid enter_user_mode(Context& context, String mode_name, KeymapMode mode, bool lock)\n{\n    on_next_key_with_autoinfo(context, format(\"user.{}\", mode_name), KeymapMode::None,\n                             [mode_name, mode, lock](Key key, Context& context) mutable {\n        if (key == Key::Escape)\n            return;\n\n        if (context.keymaps().is_mapped(key, mode))\n        {\n            ScopedSetBool disable_keymaps(context.keymaps_disabled());\n\n            InputHandler::ScopedForceNormal force_normal{context.input_handler(), {}};\n\n            ScopedEdition edition(context);\n            for (auto& key : context.keymaps().get_mapping_keys(key, mode))\n                context.input_handler().handle_key(key);\n        }\n\n        if (lock)\n            enter_user_mode(context, std::move(mode_name), mode, true);\n    }, lock ? format(\"{} (lock)\", mode_name) : mode_name,\n    build_autoinfo_for_mapping(context, mode, {}));\n}\n\nconst CommandDesc enter_user_mode_cmd = {\n    \"enter-user-mode\",\n    nullptr,\n    \"enter-user-mode [<switches>] <name>: enable <name> keymap mode for next key\",\n    ParameterDesc{\n        { { \"lock\", { {}, \"stay in mode until <esc> is pressed\" } } },\n        ParameterDesc::Flags::None, 1, 1\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    [](const Context& context,\n       CommandParameters params, size_t token_to_complete,\n       ByteCount pos_in_token) -> Completions\n    {\n        if (token_to_complete == 0)\n        {\n            return { 0_byte, params[0].length(),\n                     complete(params[0], pos_in_token, context.keymaps().user_modes()),\n                     Completions::Flags::Menu };\n        }\n        return {};\n    },\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        auto lock = (bool)parser.get_switch(\"lock\");\n        KeymapMode mode = parse_keymap_mode(parser[0], context.keymaps().user_modes());\n        enter_user_mode(context, parser[0], mode, lock);\n    }\n};\n\nconst CommandDesc provide_module_cmd = {\n    \"provide-module\",\n    nullptr,\n    \"provide-module [<switches>] <name> <cmds>: declares a module <name> provided by <cmds>\",\n    ParameterDesc{\n        { { \"override\", { {}, \"allow overriding an existing module\" } } },\n        ParameterDesc::Flags::None,\n        2, 2\n    },\n    CommandFlags::None,\n    CommandHelper{},\n    CommandCompleter{},\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        const String& module_name = parser[0];\n        auto& cm = CommandManager::instance();\n\n        if (not all_of(module_name, is_identifier))\n            throw runtime_error(format(\"invalid module name: '{}'\", module_name));\n\n        if (cm.module_defined(module_name) and not parser.get_switch(\"override\"))\n            throw runtime_error(format(\"module '{}' already defined\", module_name));\n        cm.register_module(module_name, parser[1]);\n    }\n};\n\nconst CommandDesc require_module_cmd = {\n    \"require-module\",\n    nullptr,\n    \"require-module <name>: ensures that <name> module has been loaded\",\n    single_param,\n    CommandFlags::None,\n    CommandHelper{},\n    make_completer(menu(\n         [](const Context&, StringView prefix, ByteCount cursor_pos) {\n            return CommandManager::instance().complete_module_name(prefix.substr(0, cursor_pos));\n        })),\n    [](const ParametersParser& parser, Context& context, const ShellContext&)\n    {\n        CommandManager::instance().load_module(parser[0], context);\n    }\n};\n\n}\n\nvoid register_commands()\n{\n    CommandManager& cm = CommandManager::instance();\n    cm.register_command(\"nop\", [](const ParametersParser&, Context&, const ShellContext&){}, \"do nothing\",\n        {{}, ParameterDesc::Flags::IgnoreUnknownSwitches});\n\n    auto register_command = [&](const CommandDesc& c)\n    {\n        cm.register_command(c.name, c.func, c.docstring, c.params, c.flags, c.helper, c.completer);\n        if (c.alias)\n            GlobalScope::instance().aliases().add_alias(c.alias, c.name);\n    };\n\n    register_command(edit_cmd);\n    register_command(force_edit_cmd);\n    register_command(write_cmd);\n    register_command(force_write_cmd);\n    register_command(write_all_cmd);\n    register_command(write_all_quit_cmd);\n    register_command(kill_cmd);\n    register_command(force_kill_cmd);\n    register_command(daemonize_session_cmd);\n    register_command(quit_cmd);\n    register_command(force_quit_cmd);\n    register_command(write_quit_cmd);\n    register_command(force_write_quit_cmd);\n    register_command(buffer_cmd);\n    register_command(buffer_next_cmd);\n    register_command(buffer_previous_cmd);\n    register_command(delete_buffer_cmd);\n    register_command(force_delete_buffer_cmd);\n    register_command(rename_buffer_cmd);\n    register_command(arrange_buffers_cmd);\n    register_command(add_highlighter_cmd);\n    register_command(remove_highlighter_cmd);\n    register_command(add_hook_cmd);\n    register_command(remove_hook_cmd);\n    register_command(trigger_user_hook_cmd);\n    register_command(define_command_cmd);\n    register_command(complete_command_cmd);\n    register_command(alias_cmd);\n    register_command(unalias_cmd);\n    register_command(echo_cmd);\n    register_command(debug_cmd);\n    register_command(source_cmd);\n    register_command(set_option_cmd);\n    register_command(unset_option_cmd);\n    register_command(update_option_cmd);\n    register_command(declare_option_cmd);\n    register_command(map_key_cmd);\n    register_command(unmap_key_cmd);\n    register_command(execute_keys_cmd);\n    register_command(evaluate_commands_cmd);\n    register_command(prompt_cmd);\n    register_command(on_key_cmd);\n    register_command(info_cmd);\n    register_command(try_catch_cmd);\n    register_command(set_face_cmd);\n    register_command(unset_face_cmd);\n    register_command(rename_client_cmd);\n    register_command(set_register_cmd);\n    register_command(select_cmd);\n    register_command(change_directory_cmd);\n    register_command(rename_session_cmd);\n    register_command(fail_cmd);\n    register_command(declare_user_mode_cmd);\n    register_command(enter_user_mode_cmd);\n    register_command(provide_module_cmd);\n    register_command(require_module_cmd);\n}\n\n}\n"
  },
  {
    "path": "src/commands.hh",
    "content": "#ifndef commands_hh_INCLUDED\n#define commands_hh_INCLUDED\n\nnamespace Kakoune\n{\n\nvoid register_commands();\n\nstruct kill_session\n{\n    int exit_status;\n};\n\n}\n\n#endif // commands_hh_INCLUDED\n"
  },
  {
    "path": "src/completion.cc",
    "content": "#include \"completion.hh\"\n#include \"file.hh\"\n#include \"context.hh\"\n#include \"option_types.hh\"\n#include \"option_manager.hh\"\n#include \"regex.hh\"\n\n#if defined(__APPLE__)\n#define st_mtim st_mtimespec\n#endif\n\nnamespace Kakoune\n{\n\nstatic CandidateList candidates(ConstArrayView<RankedMatch> matches, StringView dirname)\n{\n    CandidateList res;\n    res.reserve(matches.size());\n    for (auto& match : matches)\n        res.push_back(dirname + match.candidate());\n    return res;\n}\n\nCandidateList complete_filename(StringView prefix, const Regex& ignored_regex,\n                                ByteCount cursor_pos, FilenameFlags flags)\n{\n    prefix = prefix.substr(0, cursor_pos);\n    auto [dirname, fileprefix] = split_path(prefix);\n    auto parsed_dirname = parse_filename(dirname);\n\n    Optional<ThreadedRegexVM<const char*, RegexMode::Forward | RegexMode::AnyMatch | RegexMode::NoSaves>> vm;\n    if (not ignored_regex.empty())\n    {\n        vm.emplace(*ignored_regex.impl());\n        if (vm->exec(fileprefix.begin(), fileprefix.end(), fileprefix.begin(), fileprefix.end(), RegexExecFlags::None))\n            vm.reset();\n    }\n\n    const bool only_dirs = (flags & FilenameFlags::OnlyDirectories);\n\n    Vector<String> files;\n    list_files(parsed_dirname, [&](StringView filename, const struct stat& st) {\n        if ((not vm or not vm->exec(filename.begin(), filename.end(),\n                                    filename.begin(), filename.end(),\n                                    RegexExecFlags::None)) and\n               (not only_dirs or S_ISDIR(st.st_mode)))\n            files.push_back(filename.str());\n    });\n    Vector<RankedMatch> matches;\n    for (auto& file : files)\n    {\n        if (RankedMatch match{file, fileprefix})\n            matches.push_back(match);\n    }\n    // Hack: when completing directories, also echo back the query if it\n    // is a valid directory. This enables menu completion to select the\n    // directory instead of a child.\n    if (only_dirs and not dirname.empty() and dirname.back() == '/' and fileprefix.empty()\n        and /* exists on disk */ not files.empty())\n    {\n        matches.push_back(RankedMatch{fileprefix, fileprefix});\n    }\n    std::sort(matches.begin(), matches.end());\n    const bool expand = (flags & FilenameFlags::Expand);\n    return candidates(matches, expand ? parsed_dirname : dirname);\n}\n\nCandidateList complete_command(StringView prefix, ByteCount cursor_pos)\n{\n    String real_prefix = parse_filename(prefix.substr(0, cursor_pos));\n    auto [dirname, fileprefix] = split_path(real_prefix);\n\n    if (not dirname.empty())\n    {\n        Vector<String> files;\n        list_files(dirname, [&](StringView filename, const struct stat& st) {\n            bool executable = st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH);\n            if (S_ISDIR(st.st_mode) or (S_ISREG(st.st_mode) and executable))\n                files.push_back(filename.str());\n        });\n        Vector<RankedMatch> matches;\n        for (auto& file : files)\n        {\n            if (RankedMatch match{file, fileprefix})\n                matches.push_back(match);\n        }\n        std::sort(matches.begin(), matches.end());\n        return candidates(matches, dirname);\n    }\n\n    using TimeSpec = decltype(stat::st_mtim);\n\n    struct CommandCache\n    {\n        TimeSpec mtim = {};\n        Vector<String, MemoryDomain::Completion> commands;\n    };\n    static HashMap<String, CommandCache, MemoryDomain::Completion> command_cache;\n\n    Vector<RankedMatch> matches;\n    for (auto dir : StringView{getenv(\"PATH\")} | split<StringView>(':'))\n    {\n        auto dirname = ((not dir.empty() and dir.back() == '/') ? dir.substr(0, dir.length()-1) : dir).str();\n\n        struct stat st;\n        if (stat(dirname.c_str(), &st))\n            continue;\n\n        auto& cache = command_cache[dirname];\n        if (memcmp(&cache.mtim, &st.st_mtim, sizeof(TimeSpec)) != 0)\n        {\n            cache.commands.clear();\n            list_files(dirname, [&](StringView filename, const struct stat& st) {\n                bool executable = st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH);\n                if (S_ISREG(st.st_mode) and executable)\n                    cache.commands.push_back(filename.str());\n            });\n            memcpy(&cache.mtim, &st.st_mtim, sizeof(TimeSpec));\n        }\n        for (auto& cmd : cache.commands)\n        {\n            if (RankedMatch match{cmd, fileprefix})\n                matches.push_back(match);\n        }\n    }\n    std::sort(matches.begin(), matches.end());\n    auto it = std::unique(matches.begin(), matches.end());\n    matches.erase(it, matches.end());\n    return candidates(matches, \"\");\n}\n\nCompletions shell_complete(const Context& context, StringView prefix, ByteCount cursor_pos)\n{\n    ByteCount word_start = 0;\n    ByteCount word_end = 0;\n\n    bool command = true;\n    const ByteCount len = prefix.length();\n    for (ByteCount pos = 0; pos < cursor_pos and pos < len;)\n    {\n        command = (pos == 0 or prefix[pos-1] == ';' or prefix[pos-1] == '|' or\n                   (pos > 1 and prefix[pos-1] == '&' and prefix[pos-2] == '&'));\n        while (pos != len and is_horizontal_blank(prefix[pos]))\n            ++pos;\n        word_start = pos;\n        while (pos != len and not is_horizontal_blank(prefix[pos]))\n            ++pos;\n        word_end = pos;\n    }\n    Completions completions{word_start, word_end};\n    if (command)\n        completions.candidates = complete_command(prefix.substr(word_start, word_end),\n                                                  cursor_pos - word_start);\n    else\n        completions.candidates = complete_filename(prefix.substr(word_start, word_end),\n                                                   context.options()[\"ignored_files\"].get<Regex>(),\n                                                   cursor_pos - word_start);\n    return completions;\n}\n\n}\n"
  },
  {
    "path": "src/completion.hh",
    "content": "#ifndef completion_hh_INCLUDED\n#define completion_hh_INCLUDED\n\n#include <algorithm>\n\n#include \"units.hh\"\n#include \"string.hh\"\n#include \"vector.hh\"\n#include \"ranked_match.hh\"\n\nnamespace Kakoune\n{\n\nclass Context;\nclass Regex;\n\nusing CandidateList = Vector<String, MemoryDomain::Completion>;\n\nstruct Completions\n{\n    enum class Flags\n    {\n        None = 0,\n        Quoted = 0b1,\n        Menu = 0b10,\n        NoEmpty = 0b100\n    };\n\n    constexpr friend bool with_bit_ops(Meta::Type<Flags>) { return true; }\n\n    CandidateList candidates;\n    ByteCount start;\n    ByteCount end;\n    Flags flags = Flags::None;\n\n    Completions()\n        : start(0), end(0) {}\n\n    Completions(ByteCount start, ByteCount end)\n        : start(start), end(end) {}\n\n    Completions(ByteCount start, ByteCount end, CandidateList candidates, Flags flags = Flags::None)\n        : candidates(std::move(candidates)), start(start), end(end), flags{flags} {}\n};\n\ninline Completions complete_nothing(const Context&, StringView, ByteCount cursor_pos)\n{\n    return {cursor_pos, cursor_pos};\n}\n\nenum class FilenameFlags\n{\n    None = 0,\n    OnlyDirectories = 1 << 0,\n    Expand = 1 << 1\n};\nconstexpr bool with_bit_ops(Meta::Type<FilenameFlags>) { return true; }\n\nCandidateList complete_filename(StringView prefix, const Regex& ignore_regex,\n                                ByteCount cursor_pos = -1,\n                                FilenameFlags flags = FilenameFlags::None);\n\nCandidateList complete_command(StringView prefix, ByteCount cursor_pos = -1);\n\nCompletions shell_complete(const Context& context, StringView, ByteCount cursor_pos);\n\ninline Completions offset_pos(Completions completion, ByteCount offset)\n{\n    return {completion.start + offset, completion.end + offset,\n            std::move(completion.candidates), completion.flags};\n}\n\ntemplate<typename Container>\nCandidateList complete(StringView query, ByteCount cursor_pos,\n                       const Container& container)\n{\n    using std::begin;\n    static_assert(not std::is_same<decltype(*begin(container)), String>::value,\n                  \"complete require long lived strings, not temporaries\");\n\n    query = query.substr(0, cursor_pos);\n    Vector<RankedMatch> matches;\n    for (const auto& str : container)\n    {\n        if (RankedMatch match{str, query})\n            matches.push_back(match);\n    }\n    std::sort(matches.begin(), matches.end());\n    CandidateList res;\n    for (auto& m : matches)\n        res.push_back(m.candidate().str());\n    return res;\n}\n\n}\n#endif // completion_hh_INCLUDED\n"
  },
  {
    "path": "src/context.cc",
    "content": "#include \"context.hh\"\n\n#include \"client.hh\"\n#include \"scope.hh\"\n#include \"buffer_manager.hh\"\n#include \"register_manager.hh\"\n#include \"hook_manager.hh\"\n#include \"face_registry.hh\"\n#include \"window.hh\"\n\nnamespace Kakoune\n{\n\nContext::~Context() = default;\n\nContext::Context(InputHandler& input_handler, SelectionList selections,\n                 Flags flags, String name)\n    : m_flags(flags),\n      m_input_handler{&input_handler},\n      m_selection_history{*this, std::move(selections)},\n      m_name(std::move(name))\n{}\n\nContext::Context(EmptyContextFlag) : m_selection_history{*this} {}\n\nBuffer& Context::buffer() const\n{\n    if (not has_buffer())\n        throw runtime_error(\"no buffer in context\");\n    return const_cast<Buffer&>(selections(false).buffer());\n}\n\nWindow& Context::window() const\n{\n    if (not has_window())\n        throw runtime_error(\"no window in context\");\n    return *m_window;\n}\n\nInputHandler& Context::input_handler() const\n{\n    if (not has_input_handler())\n        throw runtime_error(\"no input handler in context\");\n    return *m_input_handler;\n}\n\nClient& Context::client() const\n{\n    if (not has_client())\n        throw runtime_error(\"no client in context\");\n    return *m_client;\n}\n\nScope& Context::scope(bool allow_local) const\n{\n    if (allow_local and not m_local_scopes.empty())\n        return *m_local_scopes.back();\n    if (has_window())\n        return window();\n    if (has_buffer())\n        return buffer();\n    return GlobalScope::instance();\n}\n\nOptionManager& Context::options() const { return scope().options(); }\nHookManager&   Context::hooks()   const { return scope().hooks(); }\nKeymapManager& Context::keymaps() const { return scope().keymaps(); }\nAliasRegistry& Context::aliases() const { return scope().aliases(); }\nFaceRegistry&  Context::faces(bool allow_local) const { return scope(allow_local).faces(); }\n\nvoid Context::set_client(Client& client)\n{\n    kak_assert(not has_client());\n    m_client.reset(&client);\n}\n\nvoid Context::set_window(Window& window)\n{\n    kak_assert(&window.buffer() == &buffer());\n    m_window.reset(&window);\n    if (not m_local_scopes.empty())\n        m_local_scopes.front()->reparent(window);\n}\n\nvoid Context::print_status(DisplayLine prompt, DisplayLine content, ColumnCount cursor_pos) const\n{\n    if (has_client())\n        client().print_status(std::move(prompt), std::move(content), cursor_pos);\n}\n\nvoid Context::print_status(DisplayLine status) const\n{\n    print_status({}, std::move(status), {-1});\n}\n\nvoid Context::push_jump(bool force)\n{\n    if (force or not (m_flags & Flags::Draft))\n        m_jump_list.push(selections());\n}\n\nvoid JumpList::push(SelectionList jump, Optional<size_t> index)\n{\n    if (index)\n    {\n        m_current = *index;\n        kak_assert(m_current <= m_jumps.size());\n    }\n\n    if (m_current != m_jumps.size())\n        m_jumps.erase(m_jumps.begin()+m_current+1, m_jumps.end());\n    m_jumps.erase(std::remove(begin(m_jumps), end(m_jumps), jump),\n                      end(m_jumps));\n    m_jumps.push_back(jump);\n    m_current = m_jumps.size();\n}\n\nconst SelectionList& JumpList::forward(Context& context, int count)\n{\n    if (m_current != m_jumps.size() and\n        m_current + count < m_jumps.size())\n    {\n        m_current += count;\n        SelectionList& res = m_jumps[m_current];\n        res.update();\n        context.print_status({ format(\"jumped to #{} ({})\",\n                               m_current, m_jumps.size() - 1),\n                               context.faces()[\"Information\"] });\n        return res;\n    }\n    throw runtime_error(\"no next jump\");\n}\n\nconst SelectionList& JumpList::backward(Context& context, int count)\n{\n    const SelectionList& current = context.selections();\n    const bool should_push_current =\n        m_current == m_jumps.size() or\n        (m_current != m_jumps.size() and m_jumps[m_current] != current);\n    if (should_push_current)\n        push(current);\n\n    const int steps = count + (should_push_current ? 1 : 0);\n    if ((int)m_current - steps < 0)\n        throw runtime_error(\"no previous jump\");\n\n    m_current -= steps;\n    SelectionList& res = m_jumps[m_current];\n    res.update();\n    context.print_status({ format(\"jumped to #{} ({})\",\n                           m_current, m_jumps.size() - 1),\n                           context.faces()[\"Information\"] });\n    return res;\n}\n\nvoid JumpList::forget_buffer(Buffer& buffer)\n{\n    for (size_t i = 0; i < m_jumps.size();)\n    {\n        if (&m_jumps[i].buffer() == &buffer)\n        {\n            if (i < m_current)\n                --m_current;\n            else if (i == m_current)\n                m_current = m_jumps.size()-1;\n\n            m_jumps.erase(m_jumps.begin() + i);\n        }\n        else\n            ++i;\n    }\n}\n\nContext::SelectionHistory::SelectionHistory(Context& context) : m_context(context) {}\n\nContext::SelectionHistory::SelectionHistory(Context& context, SelectionList selections)\n    : m_context(context),\n      m_history{HistoryNode{std::move(selections), HistoryId::Invalid}},\n      m_history_id(HistoryId::First) {}\n\nvoid Context::SelectionHistory::initialize(SelectionList selections)\n{\n    kak_assert(empty());\n    m_history = {HistoryNode{std::move(selections), HistoryId::Invalid}};\n    m_history_id = HistoryId::First;\n}\n\nSelectionList& Context::SelectionHistory::selections(bool update)\n{\n    if (empty())\n        throw runtime_error(\"no selections in context\");\n    auto& sels = m_staging ? m_staging->selections : current_history_node().selections;\n    if (update)\n        sels.update();\n    return sels;\n}\n\nvoid Context::SelectionHistory::begin_edition()\n{\n    if (not in_edition())\n        m_staging = HistoryNode{selections(), m_history_id};\n    m_in_edition.set();\n}\n\nvoid Context::SelectionHistory::end_edition()\n{\n    kak_assert(in_edition());\n    m_in_edition.unset();\n    if (in_edition())\n        return;\n\n    if (m_history_id != HistoryId::Invalid and current_history_node().selections == m_staging->selections)\n    {\n        // No change, except maybe the index of the main selection.\n        // Update timestamp to potentially improve interaction with content undo.\n        auto& node = current_history_node();\n        node.selections.force_timestamp(m_staging->selections.timestamp());\n        node.selections.set_main_index(m_staging->selections.main_index());\n    }\n    else\n    {\n        m_history_id = next_history_id();\n        m_history.push_back(std::move(*m_staging));\n    }\n    m_staging.reset();\n}\n\ntemplate<Direction direction>\nvoid Context::SelectionHistory::undo()\n{\n    static constexpr bool backward = direction == Backward;\n    if (in_edition())\n        throw runtime_error(\"selection undo is only supported at top-level\");\n    kak_assert(not empty());\n    SelectionList old_selections = selections();\n    HistoryId next;\n    do\n    {\n        if constexpr (backward)\n            next = current_history_node().parent;\n        else\n            next = current_history_node().redo_child;\n        if (next == HistoryId::Invalid)\n            throw runtime_error(backward ? \"no selection change to undo\" : \"no selection change to redo\");\n        auto select_next = [&, next] {\n            HistoryId previous_id = m_history_id;\n            m_history_id = next;\n            if constexpr (backward)\n                current_history_node().redo_child = previous_id;\n        };\n        Buffer& destination_buffer = history_node(next).selections.buffer();\n        if (&destination_buffer == &m_context.buffer())\n            select_next();\n        else\n            m_context.change_buffer(destination_buffer, { std::move(select_next) });\n    }\n    while (selections() == old_selections);\n}\n\nvoid Context::SelectionHistory::forget_buffer(Buffer& buffer)\n{\n    Vector<HistoryId, MemoryDomain::Selections> new_ids;\n    size_t bias = 0;\n    for (size_t i = 0; i < m_history.size(); ++i)\n    {\n        auto& node = history_node((HistoryId)i);\n        HistoryId id;\n        if (&node.selections.buffer() == &buffer)\n        {\n            id = HistoryId::Invalid;\n            ++bias;\n        }\n        else\n            id = (HistoryId)(i - bias);\n        new_ids.push_back(id);\n    }\n    auto new_id = [&new_ids](HistoryId old_id) -> HistoryId {\n        return old_id == HistoryId::Invalid ? HistoryId::Invalid : new_ids[(size_t)old_id];\n    };\n\n    m_history.erase(remove_if(m_history, [&buffer](const auto& node) {\n        return &node.selections.buffer() == &buffer;\n    }), m_history.end());\n\n    for (auto& node : m_history)\n    {\n        node.parent = new_id(node.parent);\n        node.redo_child = new_id(node.redo_child);\n    }\n    m_history_id = new_id(m_history_id);\n    if (m_staging)\n    {\n        m_staging->parent = new_id(m_staging->parent);\n        kak_assert(m_staging->redo_child == HistoryId::Invalid);\n    }\n    kak_assert(m_history_id != HistoryId::Invalid or m_staging);\n}\n\nvoid Context::change_buffer(Buffer& buffer, Optional<FunctionRef<void()>> set_selections)\n{\n    if (has_buffer() and &buffer == &this->buffer())\n        return;\n\n    if (has_buffer() and m_edition_level > 0)\n       this->buffer().commit_undo_group();\n\n    if (has_client())\n    {\n        client().info_hide();\n        client().menu_hide();\n        client().change_buffer(buffer, std::move(set_selections));\n    }\n    else\n    {\n        m_window.reset();\n        if (m_selection_history.empty())\n            m_selection_history.initialize(SelectionList{buffer, Selection{}});\n        else\n        {\n            ScopedSelectionEdition selection_edition{*this};\n            selections_write_only() = SelectionList{buffer, Selection{}};\n        }\n        if (not m_local_scopes.empty())\n            m_local_scopes.front()->reparent(buffer);\n    }\n\n    if (has_input_handler())\n        input_handler().reset_normal_mode();\n}\n\nvoid Context::forget_buffer(Buffer& buffer)\n{\n    m_jump_list.forget_buffer(buffer);\n\n    if (&this->buffer() == &buffer)\n    {\n        if (is_editing() && has_input_handler())\n            input_handler().reset_normal_mode();\n\n        auto last_buffer = this->last_buffer();\n        change_buffer(last_buffer ? *last_buffer : BufferManager::instance().get_first_buffer());\n    }\n\n    m_selection_history.forget_buffer(buffer);\n}\n\nBuffer* Context::last_buffer() const\n{\n    const auto jump_list = m_jump_list.get_as_list();\n    if (jump_list.empty())\n        return nullptr;\n\n    auto predicate = [this](const auto& sels) {\n        return &sels.buffer() != &this->buffer();\n    };\n\n    auto next_buffer = find_if(jump_list.subrange(m_jump_list.current_index()-1),\n                               predicate);\n    if (next_buffer != jump_list.end())\n        return &next_buffer->buffer();\n\n    auto previous_buffer = find_if(jump_list.subrange(0, m_jump_list.current_index()) | reverse(),\n                                   predicate);\n\n    return previous_buffer != jump_list.rend() ? &previous_buffer->buffer() : nullptr;\n}\n\nSelectionList& Context::selections(bool update)\n{\n    return m_selection_history.selections(update);\n}\n\ntemplate<Direction direction>\nvoid Context::undo_selection_change()\n{\n    m_selection_history.undo<direction>();\n}\ntemplate void Context::undo_selection_change<Backward>();\ntemplate void Context::undo_selection_change<Forward>();\n\nSelectionList& Context::selections_write_only()\n{\n    return selections(false);\n}\n\nconst SelectionList& Context::selections(bool update) const\n{\n    return const_cast<Context&>(*this).selections(update);\n}\n\nVector<String> Context::selections_content() const\n{\n    auto& buf = buffer();\n    Vector<String> contents;\n    for (auto& sel : selections())\n        contents.push_back(buf.string(sel.min(), buf.char_next(sel.max())));\n    return contents;\n}\n\nvoid Context::begin_edition()\n{\n    if (m_edition_level >= 0)\n    {\n        if (m_edition_level == 0)\n            m_edition_timestamp = buffer().timestamp();\n        ++m_edition_level;\n    }\n}\n\nvoid Context::end_edition()\n{\n    if (m_edition_level < 0)\n        return;\n\n    kak_assert(m_edition_level != 0);\n    if (m_edition_level == 1 and\n        buffer().timestamp() != m_edition_timestamp)\n        buffer().commit_undo_group();\n\n    --m_edition_level;\n}\n\nStringView Context::main_sel_register_value(StringView reg) const\n{\n    size_t index = has_buffer() ? selections(false).main_index() : 0;\n    return RegisterManager::instance()[reg].get_main(*this, index);\n}\n\nvoid Context::set_name(String name) {\n    String old_name = std::exchange(m_name, std::move(name));\n    hooks().run_hook(Hook::ClientRenamed, format(\"{}:{}\", old_name, m_name), *this);\n}\n\nScopedEdition::ScopedEdition(Context& context)\n    : m_context{context},\n      m_buffer{context.has_buffer() ? &context.buffer() : nullptr}\n{ if (m_buffer) m_context.begin_edition(); }\n\nScopedEdition::~ScopedEdition() { if (m_buffer) m_context.end_edition(); }\n\nScopedSelectionEdition::ScopedSelectionEdition(Context& context)\n    : m_context{context},\n      m_valid{not (m_context.flags() & Context::Flags::Draft) and context.has_buffer()}\n{ if (m_valid) m_context.m_selection_history.begin_edition(); }\n\nScopedSelectionEdition::ScopedSelectionEdition(ScopedSelectionEdition&& other) : m_context{other.m_context}, m_valid{other.m_valid}\n{ other.m_valid = false; }\n\nScopedSelectionEdition::~ScopedSelectionEdition() { if (m_valid) m_context.m_selection_history.end_edition(); }\n\n}\n"
  },
  {
    "path": "src/context.hh",
    "content": "#ifndef context_hh_INCLUDED\n#define context_hh_INCLUDED\n\n#include \"selection.hh\"\n#include \"optional.hh\"\n#include \"utils.hh\"\n#include \"function.hh\"\n\nnamespace Kakoune\n{\n\nclass Context;\nclass Window;\nclass Buffer;\nclass Client;\nclass Scope;\nclass InputHandler;\nclass DisplayLine;\n\nclass AliasRegistry;\nclass FaceRegistry;\nclass OptionManager;\nclass KeymapManager;\nclass HookManager;\n\nenum Direction { Backward = -1, Forward = 1 };\n\nstruct JumpList\n{\n    void push(SelectionList jump, Optional<size_t> index = {});\n    const SelectionList& forward(Context& context, int count);\n    const SelectionList& backward(Context& context, int count);\n    void forget_buffer(Buffer& buffer);\n\n    friend bool operator==(const JumpList& lhs, const JumpList& rhs) = default;\n\n    size_t current_index() const { return m_current; }\n\n    ConstArrayView<SelectionList> get_as_list() const { return m_jumps; }\n\nprivate:\n    using Contents = Vector<SelectionList, MemoryDomain::Selections>;\n    Contents m_jumps;\n    size_t m_current = 0;\n};\n\nusing LastSelectFunc = Function<void (Context&)>;\n\nstruct LocalScope;\n\n// A Context is used to access non singleton objects for various services\n// in commands.\n//\n// The Context object links a Client, a Window, an InputHandler and a\n// SelectionList. It may represent an interactive user window, a hook\n// execution or a macro replay context.\nclass Context\n{\npublic:\n    enum class Flags\n    {\n        None  = 0,\n        Draft = 1,\n    };\n    friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; }\n\n    Context(InputHandler& input_handler, SelectionList selections,\n            Flags flags, String name = \"\");\n\n    struct EmptyContextFlag {};\n    explicit Context(EmptyContextFlag);\n    ~Context();\n\n    Context(const Context&) = delete;\n    Context& operator=(const Context&) = delete;\n\n    Buffer& buffer() const;\n    bool has_buffer() const { return not m_selection_history.empty(); }\n\n    Window& window() const;\n    bool has_window() const { return (bool)m_window; }\n\n    Client& client() const;\n    bool has_client() const { return (bool)m_client; }\n\n    InputHandler& input_handler() const;\n    bool has_input_handler() const { return (bool)m_input_handler; }\n\n    SelectionList& selections(bool update = true);\n    const SelectionList& selections(bool update = true) const;\n    Vector<String>  selections_content() const;\n\n    // Return potentially out of date selections\n    SelectionList& selections_write_only();\n\n    void end_selection_edition() { m_selection_history.end_edition(); }\n    template<Direction direction>\n    void undo_selection_change();\n\n    void change_buffer(Buffer& buffer, Optional<FunctionRef<void()>> set_selection = {});\n    void forget_buffer(Buffer& buffer);\n\n    void set_client(Client& client);\n    void set_window(Window& window);\n\n    friend struct LocalScope;\n\n    Scope& scope(bool allow_local = true) const;\n    Scope* local_scope() const { return m_local_scopes.empty() ? nullptr : m_local_scopes.back(); }\n\n    OptionManager& options() const;\n    HookManager&   hooks()   const;\n    KeymapManager& keymaps() const;\n    AliasRegistry& aliases() const;\n    FaceRegistry&  faces(bool allow_local = true) const;\n\n    void print_status(DisplayLine prompt, DisplayLine content, ColumnCount cursor_pos) const;\n    void print_status(DisplayLine content) const;\n\n    StringView main_sel_register_value(StringView reg) const;\n\n    const String& name() const { return m_name; }\n    void set_name(String name);\n\n    bool is_editing() const { return m_edition_level!= 0; }\n    void disable_undo_handling() { m_edition_level = -1; }\n\n    NestedBool& hooks_disabled() { return m_hooks_disabled; }\n    const NestedBool& hooks_disabled() const { return m_hooks_disabled; }\n\n    NestedBool& keymaps_disabled() { return m_keymaps_disabled; }\n    const NestedBool& keymaps_disabled() const { return m_keymaps_disabled; }\n\n    Flags flags() const { return m_flags; }\n\n    JumpList& jump_list() { return m_jump_list; }\n    void push_jump(bool force = false);\n\n    template<typename Func>\n    void set_last_select(Func&& last_select) { m_last_select = std::forward<Func>(last_select); }\n\n    void repeat_last_select() { if (m_last_select) m_last_select(*this); }\n\n    Buffer* last_buffer() const;\n\n    bool ensure_cursor_visible = true;\nprivate:\n    void begin_edition();\n    void end_edition();\n    int m_edition_level = 0;\n    size_t m_edition_timestamp = 0;\n\n    friend struct ScopedEdition;\n    friend struct ScopedSelectionEdition;\n\n    Flags m_flags = Flags::None;\n\n    SafePtr<InputHandler> m_input_handler;\n    SafePtr<Window>       m_window;\n    SafePtr<Client>       m_client;\n    Vector<Scope*>   m_local_scopes;\n\n    class SelectionHistory {\n    public:\n        SelectionHistory(Context& context);\n        SelectionHistory(Context& context, SelectionList selections);\n        void initialize(SelectionList selections);\n        bool empty() const { return m_history.empty() and not m_staging; }\n        SelectionList& selections(bool update = true);\n\n        void begin_edition();\n        void end_edition();\n        bool in_edition() const { return m_in_edition; }\n\n        template<Direction direction>\n        void undo();\n        void forget_buffer(Buffer& buffer);\n    private:\n        enum class HistoryId : size_t { First = 0, Invalid = (size_t)-1 };\n\n        struct HistoryNode\n        {\n            HistoryNode(SelectionList selections, HistoryId parent) : selections(selections), parent(parent) {}\n\n            SelectionList selections;\n            HistoryId parent;\n            HistoryId redo_child = HistoryId::Invalid;\n        };\n\n              HistoryId next_history_id() const noexcept    { return (HistoryId)m_history.size(); }\n              HistoryNode& history_node(HistoryId id)       { return m_history[(size_t)id]; }\n        const HistoryNode& history_node(HistoryId id) const { return m_history[(size_t)id]; }\n              HistoryNode& current_history_node()           { kak_assert((size_t)m_history_id < m_history.size()); return m_history[(size_t)m_history_id]; }\n\n        Context&              m_context;\n        Vector<HistoryNode>   m_history;\n        HistoryId             m_history_id = HistoryId::Invalid;\n        Optional<HistoryNode> m_staging;\n        NestedBool            m_in_edition;\n    };\n    SelectionHistory m_selection_history;\n\n    String m_name;\n\n    JumpList m_jump_list;\n\n    LastSelectFunc m_last_select;\n\n    NestedBool m_hooks_disabled;\n    NestedBool m_keymaps_disabled;\n};\n\nstruct ScopedEdition\n{\n    ScopedEdition(Context& context);\n    ~ScopedEdition();\n    ScopedEdition(const ScopedEdition&) = delete;\n\n    Context& context() const { return m_context; }\nprivate:\n    Context& m_context;\n    SafePtr<Buffer> m_buffer;\n};\n\nstruct ScopedSelectionEdition\n{\n    ScopedSelectionEdition(Context& context);\n    ScopedSelectionEdition(ScopedSelectionEdition&& other);\n    ~ScopedSelectionEdition();\n\nprivate:\n    Context& m_context;\n    bool m_valid;\n};\n\n}\n#endif // context_hh_INCLUDED\n"
  },
  {
    "path": "src/coord.hh",
    "content": "#ifndef coord_hh_INCLUDED\n#define coord_hh_INCLUDED\n\n#include \"units.hh\"\n#include \"hash.hh\"\n\nnamespace Kakoune\n{\n\ntemplate<typename EffectiveType, typename LineType, typename ColumnType>\nstruct LineAndColumn\n{\n    LineType   line = 0;\n    ColumnType column = 0;\n\n    [[gnu::always_inline]]\n    constexpr EffectiveType operator+(EffectiveType other) const\n    {\n        return {line + other.line, column + other.column};\n    }\n\n    [[gnu::always_inline]]\n    EffectiveType& operator+=(EffectiveType other)\n    {\n        line   += other.line;\n        column += other.column;\n        return *static_cast<EffectiveType*>(this);\n    }\n\n    [[gnu::always_inline]]\n    constexpr EffectiveType operator-(EffectiveType other) const\n    {\n        return {line - other.line, column - other.column};\n    }\n\n    [[gnu::always_inline]]\n    EffectiveType& operator-=(EffectiveType other)\n    {\n        line   -= other.line;\n        column -= other.column;\n        return *static_cast<EffectiveType*>(this);\n    }\n\n    constexpr friend auto operator<=>(const LineAndColumn& lhs, const LineAndColumn& rhs) = default;\n    constexpr friend bool operator==(const LineAndColumn& lhs, const LineAndColumn& rhs) = default;\n\n    friend constexpr size_t hash_value(const EffectiveType& val)\n    {\n        return hash_values(val.line, val.column);\n    }\n};\n\nstruct BufferCoord : LineAndColumn<BufferCoord, LineCount, ByteCount>\n{\n    [[gnu::always_inline]]\n    constexpr BufferCoord(LineCount line = 0, ByteCount column = 0)\n        : LineAndColumn{line, column} {}\n\n    constexpr friend auto operator<=>(const BufferCoord& lhs, const BufferCoord& rhs) = default;\n    constexpr friend bool operator==(const BufferCoord& lhs, const BufferCoord& rhs) = default;\n};\n\nstruct DisplayCoord : LineAndColumn<DisplayCoord, LineCount, ColumnCount>\n{\n    [[gnu::always_inline]]\n    constexpr DisplayCoord(LineCount line = 0, ColumnCount column = 0)\n        : LineAndColumn{line, column} {}\n\n    constexpr friend auto operator<=>(const DisplayCoord& lhs, const DisplayCoord& rhs) = default;\n    constexpr friend bool operator==(const DisplayCoord& lhs, const DisplayCoord& rhs) = default;\n\n    static constexpr const char* option_type_name = \"coord\";\n};\n\nstruct BufferCoordAndTarget : BufferCoord\n{\n    [[gnu::always_inline]]\n    constexpr BufferCoordAndTarget(LineCount line = 0, ByteCount column = 0, ColumnCount target = -1, ColumnCount display_target=-1)\n        : BufferCoord{line, column}, target{target}, display_target{display_target} {}\n\n    [[gnu::always_inline]]\n    constexpr BufferCoordAndTarget(BufferCoord coord, ColumnCount target = -1, ColumnCount display_target=-1)\n        : BufferCoord{coord}, target{target}, display_target{display_target} {}\n\n    ColumnCount target;\n    ColumnCount display_target;\n};\n\nconstexpr size_t hash_value(const BufferCoordAndTarget& val)\n{\n    return hash_values(val.line, val.column, val.target);\n}\n\n}\n\n#endif // coord_hh_INCLUDED\n"
  },
  {
    "path": "src/debug.cc",
    "content": "#include \"debug.hh\" \n\n#include \"buffer_manager.hh\" \n#include \"buffer_utils.hh\" \n#include \"utils.hh\" \n\nnamespace Kakoune\n{\n\nvoid write_to_debug_buffer(StringView str)\n{\n    if (not BufferManager::has_instance())\n    {\n        write(2, str);\n        write(2, \"\\n\");\n        return;\n    }\n\n    constexpr StringView debug_buffer_name = \"*debug*\";\n    // Try to ensure we keep an empty line at the end of the debug buffer\n    // where the user can put its cursor to scroll with new messages\n    const bool eol_back = not str.empty() and str.back() == '\\n';\n    if (Buffer* buffer = BufferManager::instance().get_buffer_ifp(debug_buffer_name))\n    {\n        buffer->flags() &= ~Buffer::Flags::ReadOnly;\n        auto restore = OnScopeEnd([buffer] { buffer->flags() |= Buffer::Flags::ReadOnly; });\n\n        buffer->insert(buffer->back_coord(), eol_back ? str : str + \"\\n\");\n    }\n    else\n    {\n        String line = str + (eol_back ? \"\\n\" : \"\\n\\n\");\n        create_buffer_from_string(\n            debug_buffer_name.str(), Buffer::Flags::NoUndo | Buffer::Flags::Debug | Buffer::Flags::ReadOnly,\n            line);\n    }\n}\n\n}\n"
  },
  {
    "path": "src/debug.hh",
    "content": "#ifndef debug_hh_INCLUDED\n#define debug_hh_INCLUDED\n\n#include \"array.hh\"\n#include \"enum.hh\"\n\nnamespace Kakoune\n{\n\nclass StringView;\n\nenum class DebugFlags\n{\n    None     = 0,\n    Hooks    = 1 << 0,\n    Shell    = 1 << 1,\n    Profile  = 1 << 2,\n    Keys     = 1 << 3,\n    Commands = 1 << 4,\n};\n\nconstexpr bool with_bit_ops(Meta::Type<DebugFlags>) { return true; }\n\nconstexpr auto enum_desc(Meta::Type<DebugFlags>)\n{\n    return make_array<EnumDesc<DebugFlags>>({\n        { DebugFlags::Hooks, \"hooks\" },\n        { DebugFlags::Shell, \"shell\" },\n        { DebugFlags::Profile, \"profile\" },\n        { DebugFlags::Keys, \"keys\" },\n        { DebugFlags::Commands, \"commands\" },\n    });\n}\n\nvoid write_to_debug_buffer(StringView str);\n\n}\n\n#endif // debug_hh_INCLUDED\n"
  },
  {
    "path": "src/diff.hh",
    "content": "#ifndef diff_hh_INCLUDED\n#define diff_hh_INCLUDED\n\n// Implementation of the linear space variant of the algorithm described in\n// \"An O(ND) Difference Algorithm and Its Variations\"\n// (http://xmailserver.org/diff2.pdf)\n\n#include <algorithm>\n#include \"function.hh\"\n#include \"unique_ptr.hh\"\n\nnamespace Kakoune\n{\n\n// A snake is an edit followed by a (possibly empty) diagonal\nstruct Snake\n{\n    // The end points of the diagonal (x, y) -> (u, v)\n    int x, y, u, v;\n    // the edit op, reverse op happen at the end of the diagonal\n    enum Op { Add, Del, RevAdd, RevDel } op;\n};\n\ntemplate<bool forward, typename IteratorA, typename IteratorB, typename Equal>\nSnake find_end_snake_of_further_reaching_dpath(IteratorA a, int N, IteratorB b, int M,\n                                               const int* V, const int D, const int k, Equal&& eq)\n{\n    const bool add = k == -D or (k != D and V[k-1] < V[k+1]);\n\n    // if diagonal on the right goes further along x than diagonal on the left,\n    // then we take a vertical edge from it to this diagonal, hence x = V[k+1]\n    // else, we take an horizontal edge from our left diagonal,x = V[k-1]+1\n    const int x = add ? V[k+1] : V[k-1]+1;\n    // we are by construction on diagonal k, so our position along b (y) is x - k.\n    const int y = x - k;\n\n    auto at = [](auto&& base, int index, int size) -> decltype(auto) {\n        return forward ? base[index] : base[size - 1 - index];\n    };\n\n    int u = x, v = y;\n    // follow end snake along diagonal k\n    while (u < N and v < M and eq(at(a, u, N), at(b, v, M)))\n        ++u, ++v;\n\n    return { x, y, u, v, add ? Snake::Add : Snake::Del };\n}\n\ntemplate<typename IteratorA, typename IteratorB, typename Equal>\nSnake find_middle_snake(IteratorA a, int N, IteratorB b, int M,\n                        int* V1, int* V2, int cost_limit, Equal&& eq)\n{\n    const int delta = N - M;\n    V1[1] = 0;\n    V2[1] = 0;\n\n    const int max_D = std::min((M + N + 1) / 2 + 1, cost_limit);\n    for (int D = 0; D < max_D; ++D)\n    {\n        for (int k1 = -D; k1 <= D; k1 += 2)\n        {\n            auto p = find_end_snake_of_further_reaching_dpath<true>(a, N, b, M, V1, D, k1, eq);\n            V1[k1] = p.u;\n\n            const int k2 = -(k1 - delta);\n            if ((delta % 2 != 0) and -(D-1) <= k2 and k2 <= (D-1) and V1[k1] + V2[k2] >= N)\n                return p;// return last snake on forward path, len = (2 * D - 1)\n        }\n\n        for (int k2 = -D; k2 <= D; k2 += 2)\n        {\n            auto p = find_end_snake_of_further_reaching_dpath<false>(a, N, b, M, V2, D, k2, eq);\n            V2[k2] = p.u;\n\n            const int k1 = -(k2 - delta);\n            if ((delta % 2 == 0) and -D <= k1 and k1 <= D and V1[k1] + V2[k2] >= N)\n                return { N - p.u, M - p.v, N - p.x , M - p.y,\n                         (Snake::Op)(p.op + Snake::RevAdd) };// return last snake on reverse path, len = 2 * D\n        }\n    }\n\n    // We did not find a minimal path in less than max_D iterations, iterate one more time finding the best\n    Snake best{};\n    auto score = [](const Snake& s) { return s.u + s.v; };\n    for (int k1 = -max_D; k1 <= max_D; k1 += 2)\n    {\n        auto p = find_end_snake_of_further_reaching_dpath<true>(a, N, b, M, V1, max_D, k1, eq);\n        V1[k1] = p.u;\n        if ((delta % 2 != 0) and p.u <= N and p.v <= M and score(p) >= score(best))\n            best = p;\n    }\n    for (int k2 = -max_D; k2 <= max_D; k2 += 2)\n    {\n        auto p = find_end_snake_of_further_reaching_dpath<false>(a, N, b, M, V2, max_D, k2, eq);\n        V2[k2] = p.u;\n        if ((delta % 2 == 0) and p.u <= N and p.v <= M and score(p) >= score(best))\n            best = {p.x, p.y, p.u, p.v, (Snake::Op)(p.op + Snake::RevAdd)};\n    }\n\n    if (best.op >= Snake::RevAdd) // reverse the snake now, as we were comparing snake length\n        best = { N - best.u, M - best.v, N - best.x , M - best.y, best.op };\n    return best;\n}\n\nenum class DiffOp\n{\n    Keep,\n    Add,\n    Remove\n};\n\ntemplate<typename IteratorA, typename IteratorB, typename Equal, typename OnDiff>\nvoid find_diff_rec(IteratorA a, int begA, int endA,\n                   IteratorB b, int begB, int endB,\n                   int* V1, int* V2, int cost_limit,\n                   Equal&& eq, OnDiff&& on_diff)\n{\n    auto on_diff_ifn = [&](DiffOp op, int len) {\n        if (len != 0)\n            on_diff(op, len);\n    };\n\n    int prefix_len = 0;\n    while (begA != endA and begB != endB and eq(a[begA], b[begB]))\n         ++begA, ++begB, ++prefix_len;\n\n    int suffix_len = 0;\n    while (begA != endA and begB != endB and eq(a[endA-1], b[endB-1]))\n        --endA, --endB, ++suffix_len;\n\n    on_diff_ifn(DiffOp::Keep, prefix_len);\n\n    const auto lenA = endA - begA, lenB = endB - begB;\n\n    if (lenA == 0)\n        on_diff_ifn(DiffOp::Add, lenB);\n    else if (lenB == 0)\n        on_diff_ifn(DiffOp::Remove, lenA);\n    else\n    {\n        auto snake = find_middle_snake(a + begA, lenA, b + begB, lenB, V1, V2, cost_limit, eq);\n        kak_assert(snake.u <= lenA and snake.v <= lenB);\n\n        find_diff_rec(a, begA, begA + snake.x - (int)(snake.op == Snake::Del),\n                      b, begB, begB + snake.y - (int)(snake.op == Snake::Add),\n                      V1, V2, cost_limit, eq, on_diff);\n\n        if (snake.op == Snake::Add)\n            on_diff_ifn(DiffOp::Add, 1);\n        if (snake.op == Snake::Del)\n            on_diff_ifn(DiffOp::Remove, 1);\n\n        on_diff_ifn(DiffOp::Keep, snake.u - snake.x);\n\n        if (snake.op == Snake::RevAdd)\n            on_diff_ifn(DiffOp::Add, 1);\n        if (snake.op == Snake::RevDel)\n            on_diff_ifn(DiffOp::Remove, 1);\n\n        find_diff_rec(a, begA + snake.u + (int)(snake.op == Snake::RevDel), endA,\n                      b, begB + snake.v + (int)(snake.op == Snake::RevAdd), endB,\n                      V1, V2, cost_limit, eq, on_diff);\n    }\n\n    on_diff_ifn(DiffOp::Keep, suffix_len);\n}\n\nstruct Diff\n{\n    DiffOp op;\n    int len;\n};\n\ntemplate<typename IteratorA, typename IteratorB, typename OnDiff, typename Equal = std::equal_to<>>\nvoid for_each_diff(IteratorA a, int N, IteratorB b, int M, OnDiff&& on_diff, Equal&& eq = Equal{})\n{\n    const int max = 2 * (N + M) + 1;\n    UniquePtr<int[]> data(new int[2*max]);\n    constexpr int cost_limit = 1000;\n\n    Diff last{};\n    find_diff_rec(a, 0, N, b, 0, M, &data[N+M], &data[max + N+M], cost_limit, eq,\n                  [&last, &on_diff](DiffOp op, int len) {\n                      if (last.op == op)\n                          last.len += len;\n                      else\n                      {\n                          if (last.len != 0)\n                              on_diff(last.op, last.len);\n                          last = Diff{op, len};\n                      }\n                  });\n    if (last.op != DiffOp{} or last.len != 0)\n        on_diff(last.op, last.len);\n}\n\n}\n\n#endif // diff_hh_INCLUDED\n"
  },
  {
    "path": "src/display_buffer.cc",
    "content": "#include \"display_buffer.hh\"\n\n#include \"assert.hh\"\n#include \"buffer.hh\"\n#include \"face_registry.hh\"\n#include \"format.hh\"\n#include \"utf8.hh\"\n\n#include \"face_registry.hh\"\n\nnamespace Kakoune\n{\n\nBufferIterator get_iterator(const Buffer& buffer, BufferCoord coord)\n{\n    // Correct one past the end of line as next line\n    if (not buffer.is_end(coord) and coord.column == buffer[coord.line].length())\n        coord = coord.line+1;\n    return buffer.iterator_at(coord);\n}\n\nStringView DisplayAtom::content() const\n{\n    switch (m_type)\n    {\n        case Range:\n        {\n            auto line = (*m_buffer)[m_range.begin.line];\n            if (m_range.begin.line == m_range.end.line)\n                return line.substr(m_range.begin.column, m_range.end.column - m_range.begin.column);\n            else if (m_range.begin.line+1 == m_range.end.line and m_range.end.column == 0)\n                return line.substr(m_range.begin.column);\n            break;\n        }\n        case Text:\n        case ReplacedRange:\n            return m_text;\n    }\n    kak_assert(false);\n    return {};\n}\n\nColumnCount DisplayAtom::length() const\n{\n    switch (m_type)\n    {\n        case Range:\n            return utf8::column_distance(get_iterator(*m_buffer, m_range.begin),\n                                         get_iterator(*m_buffer, m_range.end));\n        case Text:\n        case ReplacedRange:\n            return m_text.column_length();\n    }\n    kak_assert(false);\n    return 0;\n}\n\nbool DisplayAtom::empty() const\n{\n    if (m_type == Range)\n        return m_range.begin == m_range.end;\n    else\n        return m_text.empty();\n}\n\nColumnCount DisplayAtom::trim_begin(ColumnCount count)\n{\n    ColumnCount res = 0;\n    if (m_type == Range)\n    {\n        auto it = get_iterator(*m_buffer, m_range.begin);\n        auto end = get_iterator(*m_buffer, m_range.end);\n        while (it != end and res < count)\n            res += codepoint_width(utf8::read_codepoint(it, end));\n        m_range.begin = std::min(it.coord(), m_range.end);\n    }\n    else\n    {\n        auto it = m_text.begin();\n        while (it != m_text.end() and res < count)\n            res += codepoint_width(utf8::read_codepoint(it, m_text.end()));\n        m_text = String{it, m_text.end()};\n    }\n\n    return res;\n}\n\nColumnCount DisplayAtom::trim_end_to_length(ColumnCount count)\n{\n    ColumnCount res = 0;\n    if (m_type == Range)\n    {\n        auto it = get_iterator(*m_buffer, m_range.begin);\n        auto end = get_iterator(*m_buffer, m_range.end);\n        while (it != end and res < count)\n            res += codepoint_width(utf8::read_codepoint(it, end));\n        m_range.end = std::min(it.coord(), m_range.end);\n    }\n    else\n    {\n        auto it = m_text.begin();\n        while (it != m_text.end() and res < count)\n            res += codepoint_width(utf8::read_codepoint(it, m_text.end()));\n        m_text = String{m_text.begin(), it};\n    }\n\n    return res;\n}\n\nDisplayLine::DisplayLine(AtomList atoms)\n    : m_atoms(std::move(atoms))\n{\n    compute_range();\n}\n\nDisplayLine::iterator DisplayLine::split(iterator it, BufferCoord pos)\n{\n    kak_assert(it->type() == DisplayAtom::Range);\n    kak_assert(it->begin() < pos);\n    kak_assert(it->end() > pos);\n\n    DisplayAtom atom = *it;\n    atom.m_range.end = pos;\n    it->m_range.begin = pos;\n    return m_atoms.insert(it, std::move(atom));\n}\n\nDisplayLine::iterator DisplayLine::split(iterator it, ColumnCount count)\n{\n    kak_assert(count > 0);\n    kak_assert(count < it->length());\n\n    if (it->type() == DisplayAtom::Text or it->type() == DisplayAtom::ReplacedRange)\n    {\n        DisplayAtom atom = *it;\n        atom.m_text = atom.m_text.substr(0, count).str();\n        it->m_text = it->m_text.substr(count).str();\n        return m_atoms.insert(it, std::move(atom));\n    }\n    auto pos = utf8::advance(get_iterator(it->buffer(), it->begin()),\n                             get_iterator(it->buffer(), it->end()),\n                             count).coord();\n    if (pos == it->begin()) // Can happen if we try to split in the middle of a multi-column codepoint\n        return m_atoms.insert(it, {it->buffer(), {pos, pos}, it->face});\n    if (pos == it->end())\n        return std::prev(m_atoms.insert(std::next(it), {it->buffer(), {pos, pos}, it->face}));\n    return split(it, pos);\n}\n\nDisplayLine::iterator DisplayLine::split(BufferCoord pos)\n{\n    auto it = find_if(begin(), end(), [pos](const DisplayAtom& a) {\n        return (a.has_buffer_range() && a.begin() >= pos) ||\n               (a.type() == DisplayAtom::Range and a.end() > pos);\n    });\n    if (it == end() or it->begin() >= pos)\n        return it;\n    return ++split(it, pos);\n}\n\nDisplayLine::iterator DisplayLine::insert(iterator it, DisplayAtom atom)\n{\n    if (atom.has_buffer_range())\n    {\n        m_range.begin  = std::min(m_range.begin, atom.begin());\n        m_range.end = std::max(m_range.end, atom.end());\n    }\n    return m_atoms.insert(it, std::move(atom));\n}\n\nDisplayAtom& DisplayLine::push_back(DisplayAtom atom)\n{\n    if (atom.has_buffer_range())\n    {\n        m_range.begin  = std::min(m_range.begin, atom.begin());\n        m_range.end = std::max(m_range.end, atom.end());\n    }\n    m_atoms.push_back(std::move(atom));\n    return m_atoms.back();\n}\n\nDisplayLine DisplayLine::extract(iterator beg, iterator end)\n{\n    if (beg == this->begin() and end == this->end())\n        return DisplayLine{std::move(*this)};\n\n    DisplayLine extracted{AtomList(std::move_iterator(beg), std::move_iterator(end))};\n    m_atoms.erase(beg, end);\n    if (extracted.m_range.begin == m_range.begin or\n        extracted.m_range.end == m_range.end)\n        compute_range(true);\n    return extracted;\n}\n\nDisplayLine::iterator DisplayLine::erase(iterator beg, iterator end)\n{\n    auto res = m_atoms.erase(beg, end);\n    compute_range(true);\n    return res;\n}\n\nvoid DisplayLine::optimize()\n{\n    if (m_atoms.empty())\n        return;\n\n    auto atom_it = m_atoms.begin();\n    for (auto next_it = atom_it + 1; next_it != m_atoms.end(); ++next_it)\n    {\n        auto& atom = *atom_it;\n        auto& next = *next_it;\n\n        const auto type = atom.type();\n        if (type == next.type() and atom.face == next.face)\n        {\n            if (type == DisplayAtom::Text)\n                atom.m_text += next.m_text;\n            else if ((type == DisplayAtom::Range or\n                      type == DisplayAtom::ReplacedRange) and\n                     next.begin() == atom.end())\n            {\n                atom.m_range.end = next.end();\n                if (type == DisplayAtom::ReplacedRange)\n                    atom.m_text += next.m_text;\n            }\n            else\n                *++atom_it = std::move(*next_it);\n        }\n        else\n            *++atom_it = std::move(*next_it);\n    }\n    m_atoms.erase(atom_it+1, m_atoms.end());\n}\n\nColumnCount DisplayLine::length() const\n{\n    ColumnCount len = 0;\n    for (auto& atom : m_atoms)\n        len += atom.length();\n    return len;\n}\n\nbool DisplayLine::trim(ColumnCount front, ColumnCount col_count)\n{\n    return trim_from(0_col, front, col_count);\n}\n\nbool DisplayLine::trim_from(ColumnCount first_col, ColumnCount front, ColumnCount col_count)\n{\n    auto it = begin();\n    while (first_col > 0 and it != end())\n    {\n        auto len = it->length();\n        if (len <= first_col)\n        {\n            ++it;\n            first_col -= len;\n        }\n        else\n        {\n            if (front > 0)\n                it = ++split(it, front);\n            first_col = 0;\n        }\n    }\n\n    auto front_it = it;\n    Optional<DisplayAtom> padding;\n    while (front > 0 and it != end())\n    {\n        front -= it->trim_begin(front);\n        kak_assert(it->empty() or front <= 0);\n        if (front < 0)\n            padding.emplace(it->has_buffer_range()\n                ? DisplayAtom{it->buffer(), {it->begin(), it->begin()}, String{' ', -front}, it->face}\n                : DisplayAtom{String{' ', -front}, it->face});\n\n        if (it->empty())\n            ++it;\n    }\n    it = m_atoms.erase(front_it, it);\n    if (padding)\n        it = m_atoms.insert(it, std::move(*padding));\n\n    it = begin();\n    for (; it != end() and col_count > 0; ++it)\n        col_count -= it->trim_end_to_length(col_count);\n    bool did_trim = it != end() && col_count == 0;\n    m_atoms.erase(it, end());\n\n    compute_range(true);\n    return did_trim;\n}\n\nconst BufferRange init_range{ {INT_MAX, INT_MAX}, {INT_MIN, INT_MIN} };\n\nvoid DisplayLine::compute_range(bool preserve_if_no_atoms)\n{\n    auto first = find_if(m_atoms, [](const auto& atom) { return atom.has_buffer_range(); });\n    if (first == m_atoms.end())\n        m_range = preserve_if_no_atoms and m_range != init_range ? m_range : BufferRange{{0, 0}, {0, 0}};\n    else\n    {\n        auto last = std::find_if(m_atoms.rbegin(), std::reverse_iterator(first+1), [](const auto& atom) { return atom.has_buffer_range(); });\n        m_range.begin = first->begin();\n        m_range.end = last->end();\n    }\n\n    kak_assert(m_range.begin <= m_range.end);\n}\n\nvoid DisplayBuffer::compute_range()\n{\n    m_range = init_range;\n    for (auto& line : m_lines)\n    {\n        m_range.begin  = std::min(line.range().begin, m_range.begin);\n        m_range.end = std::max(line.range().end, m_range.end);\n    }\n    if (m_range == init_range)\n        m_range = { { 0, 0 }, { 0, 0 } };\n    kak_assert(m_range.begin <= m_range.end);\n}\n\nvoid DisplayBuffer::optimize()\n{\n    for (auto& line : m_lines)\n        line.optimize();\n}\n\nDisplayLine parse_display_line(StringView line, Face& face, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins)\n{\n    DisplayLine res;\n    bool was_antislash = false;\n    auto pos = line.begin();\n    String content;\n    for (auto it = line.begin(), end = line.end(); it != end; ++it)\n    {\n        const char c = *it;\n        if (c == '{')\n        {\n            if (was_antislash)\n            {\n                content += StringView{pos, it};\n                content.back() = '{';\n                pos = it + 1;\n            }\n            else\n            {\n                content += StringView{pos, it};\n                if (not content.empty())\n                    res.push_back({std::move(content), face});\n                content.clear();\n                auto closing = std::find(it+1, end, '}');\n                if (closing == end)\n                    throw runtime_error(\"unclosed face definition\");\n                if (*(it+1) == '{' and closing+1 != end and *(closing+1) == '}')\n                {\n                    auto builtin_it = builtins.find(StringView{it+2, closing});\n                    if (builtin_it == builtins.end())\n                        throw runtime_error(format(\"undefined atom {}\", StringView{it+2, closing}));\n                    for (auto& atom : builtin_it->value)\n                        res.push_back(atom);\n                    // closing is now at the first char of \"}}\", advance it to the second\n                    ++closing;\n                }\n                else if (closing == it+2 and *(it+1) == '\\\\')\n                {\n                    pos = closing + 1;\n                    break;\n                }\n                else\n                    face = faces[{it+1, closing}];\n                it = closing;\n                pos = closing + 1;\n            }\n        }\n        if (c == '\\n' or c == '\\t') // line breaks and tabs are forbidden, replace with space\n        {\n            content += StringView{pos, it+1};\n            content.back() = ' ';\n            pos = it + 1;\n        }\n\n        if (c == '\\\\')\n        {\n            if (was_antislash)\n            {\n                content += StringView{pos, it};\n                pos = it + 1;\n                was_antislash = false;\n            }\n            else\n                was_antislash = true;\n        }\n        else\n            was_antislash = false;\n    }\n    content += StringView{pos, line.end()};\n    if (not content.empty())\n        res.push_back({std::move(content), face});\n    return res;\n}\n\nDisplayLine parse_display_line(StringView line, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins)\n{\n    Face face{};\n    return parse_display_line(line, face, faces, builtins);\n}\n\nDisplayLineList parse_display_line_list(StringView content, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins)\n{\n    return content | split<StringView>('\\n')\n                   | transform([&, face=Face{}](StringView s) mutable {\n                         return parse_display_line(s, face, faces, builtins);\n                     })\n                   | gather<DisplayLineList>();\n}\n\n}\n"
  },
  {
    "path": "src/display_buffer.hh",
    "content": "#ifndef display_buffer_hh_INCLUDED\n#define display_buffer_hh_INCLUDED\n\n#include \"face.hh\"\n#include \"coord.hh\"\n#include \"range.hh\"\n#include \"string.hh\"\n#include \"vector.hh\"\n#include \"hash_map.hh\"\n\n#include <algorithm>\n\nnamespace Kakoune\n{\n\nclass Buffer;\nusing BufferRange = Range<BufferCoord>;\n\nclass BufferIterator;\n// Return a buffer iterator to the coord, tolerating one past end of line coords\nBufferIterator get_iterator(const Buffer& buffer, BufferCoord coord);\n\nstruct DisplayAtom : public UseMemoryDomain<MemoryDomain::Display>\n{\npublic:\n    enum Type { Range, ReplacedRange, Text };\n\n    DisplayAtom(const Buffer& buffer, BufferRange range, Face face = {})\n        : face(face), m_type(Range), m_buffer(&buffer), m_range{range} {}\n\n    DisplayAtom(const Buffer& buffer, BufferRange range, String str, Face face = {})\n        : face(face), m_type(ReplacedRange), m_buffer(&buffer), m_range{range}, m_text{std::move(str)} {}\n\n    DisplayAtom(String str, Face face)\n        : face(face), m_type(Text), m_text(std::move(str)) {}\n\n    explicit DisplayAtom(String str)\n        : DisplayAtom(std::move(str), Face{}) {}\n\n    StringView content() const;\n    ColumnCount length() const;\n    bool empty() const;\n\n    const BufferCoord& begin() const\n    {\n        kak_assert(has_buffer_range());\n        return m_range.begin;\n    }\n\n    const BufferCoord& end() const\n    {\n        kak_assert(has_buffer_range());\n        return m_range.end;\n    }\n\n    void replace(String text)\n    {\n        if (m_type == Range)\n            m_type = ReplacedRange;\n        m_text = std::move(text);\n    }\n\n    void replace(const BufferRange& range)\n    {\n        kak_assert(m_type == Text);\n        m_type = ReplacedRange;\n        m_range = range;\n    }\n\n    bool has_buffer_range() const\n    {\n        return m_type == Range or m_type == ReplacedRange;\n    }\n\n    const Buffer& buffer() const { kak_assert(m_buffer); return *m_buffer; }\n\n    Type type() const { return m_type; }\n\n    ColumnCount trim_begin(ColumnCount count);\n    ColumnCount trim_end_to_length(ColumnCount count);\n\n    bool operator==(const DisplayAtom& other) const\n    {\n        return face == other.face and type() == other.type() and\n               content() == other.content();\n    }\n\npublic:\n    Face face;\n\nprivate:\n    friend class DisplayLine;\n\n    Type m_type;\n\n    const Buffer* m_buffer = nullptr;\n    BufferRange m_range;\n    String m_text;\n};\n\nusing AtomList = Vector<DisplayAtom, MemoryDomain::Display>;\n\nclass DisplayLine : public UseMemoryDomain<MemoryDomain::Display>\n{\npublic:\n    using iterator = AtomList::iterator;\n    using const_iterator = AtomList::const_iterator;\n    using value_type = AtomList::value_type;\n\n    DisplayLine() = default;\n    DisplayLine(AtomList atoms);\n    DisplayLine(String str, Face face)\n    { push_back({ std::move(str), face }); }\n\n    iterator begin() { return m_atoms.begin(); }\n    iterator end() { return m_atoms.end(); }\n\n    const_iterator begin() const { return m_atoms.begin(); }\n    const_iterator end() const { return m_atoms.end(); }\n\n    const AtomList& atoms() const { return m_atoms; }\n\n    ColumnCount length() const;\n    const BufferRange& range() const { return m_range; }\n\n    // Split atom pointed by it at buffer coord pos,\n    // returns an iterator to the first atom\n    iterator split(iterator it, BufferCoord pos);\n\n    // Split atom pointed by it at its pos column,\n    // returns an iterator to the first atom\n    iterator split(iterator it, ColumnCount pos);\n\n    iterator split(BufferCoord pos);\n\n    iterator insert(iterator it, DisplayAtom atom);\n\n    template<typename It>\n    iterator insert(iterator pos, It beg, It end)\n    {\n        auto has_buffer_range = [](const DisplayAtom& atom) { return atom.has_buffer_range(); };\n        auto had_range = any_of(*this, has_buffer_range);\n        if (auto first = std::find_if(beg, end, has_buffer_range); first != end)\n        {\n            auto& last = *std::find_if(std::reverse_iterator(end), std::reverse_iterator(first), has_buffer_range);\n            m_range.begin = had_range ? std::min(m_range.begin, (*first).begin()) : (*first).begin();\n            m_range.end = had_range ? std::max(m_range.end, last.end()) : last.end();\n        }\n        return m_atoms.insert(pos, beg, end);\n    }\n\n    DisplayLine extract(iterator beg, iterator end);\n    iterator erase(iterator beg, iterator end);\n    DisplayAtom& push_back(DisplayAtom atom);\n\n    // remove front from the begining of the line, and make sure\n    // the line is less that col_count character\n    bool trim(ColumnCount front, ColumnCount col_count);\n\n    // remove front from the begining of the line + first_col, and make sure\n    // the line is less that col_count character\n    bool trim_from(ColumnCount first_col, ColumnCount front, ColumnCount col_count);\n\n    // Merge together consecutive atoms sharing the same display attributes\n    void optimize();\nprivate:\n    void compute_range(bool preserve_if_no_atoms = false);\n    BufferRange m_range = { { INT_MAX, INT_MAX }, { INT_MIN, INT_MIN } };\n    AtomList  m_atoms;\n};\n\nusing DisplayLineList = Vector<DisplayLine>;\nclass FaceRegistry;\n\nDisplayLine parse_display_line(StringView line, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins = {});\nDisplayLineList parse_display_line_list(StringView content, const FaceRegistry& faces, const HashMap<String, DisplayLine>& builtins = {});\n\nclass DisplayBuffer : public UseMemoryDomain<MemoryDomain::Display>\n{\npublic:\n    DisplayBuffer() {}\n\n    DisplayLineList& lines() { return m_lines; }\n    const DisplayLineList& lines() const { return m_lines; }\n\n    // returns the smallest BufferRange which contains every DisplayAtoms\n    const BufferRange& range() const { return m_range; }\n    void compute_range();\n\n    // Optimize all lines, set DisplayLine::optimize\n    void optimize();\n\n    void set_timestamp(size_t timestamp) { m_timestamp = timestamp; }\n    size_t timestamp() const { return m_timestamp; }\n\nprivate:\n    DisplayLineList m_lines;\n    BufferRange m_range;\n    size_t m_timestamp = -1;\n};\n\n}\n\n#endif // display_buffer_hh_INCLUDED\n"
  },
  {
    "path": "src/enum.hh",
    "content": "#ifndef enum_hh_INCLUDED\n#define enum_hh_INCLUDED\n\n#include \"string.hh\"\n#include \"meta.hh\"\n\nnamespace Kakoune\n{\n\ntemplate<typename T> struct EnumDesc { T value; StringView name; };\n\ntemplate<typename T>\nconcept DescribedEnum = requires { enum_desc(Meta::Type<T>{}); };\n\n}\n\n#endif // enum_hh_INCLUDED\n"
  },
  {
    "path": "src/env_vars.cc",
    "content": "#include \"env_vars.hh\"\n\n#include \"string.hh\"\n\nextern char **environ;\n\nnamespace Kakoune\n{\n\nEnvVarMap get_env_vars()\n{\n    EnvVarMap env_vars;\n    for (char** it = environ; *it; ++it)\n    {\n        const char* name = *it;\n        const char* value = name;\n        while (*value != 0 and *value != '=')\n            ++value;\n        env_vars.insert({String{String::NoCopy{}, {name, value}},\n                         (*value == '=') ? String{String::NoCopy{}, value+1} : String{}});\n    }\n    return env_vars;\n}\n\n}\n"
  },
  {
    "path": "src/env_vars.hh",
    "content": "#ifndef env_vars_hh_INCLUDED\n#define env_vars_hh_INCLUDED\n\n#include \"hash_map.hh\"\n\nnamespace Kakoune\n{\n\nclass String;\nusing EnvVarMap = HashMap<String, String, MemoryDomain::EnvVars>;\n\nEnvVarMap get_env_vars();\n\n}\n\n#endif // env_vars_hh_INCLUDED\n"
  },
  {
    "path": "src/event_manager.cc",
    "content": "#include \"event_manager.hh\"\n\n#include \"flags.hh\"\n#include \"ranges.hh\"\n\n#if defined(__sun__)\n#include <cstring>\n#endif\n\n#include <unistd.h>\n\nnamespace Kakoune\n{\n\nFDWatcher::FDWatcher(int fd, FdEvents events, EventMode mode, Callback callback)\n    : m_fd{fd}, m_events{events}, m_mode{mode}, m_callback{std::move(callback)}\n{\n    EventManager::instance().m_fd_watchers.push_back(this);\n}\n\nFDWatcher::~FDWatcher()\n{\n    unordered_erase(EventManager::instance().m_fd_watchers, this);\n}\n\nvoid FDWatcher::run(FdEvents events, EventMode mode)\n{\n    m_callback(*this, events, mode);\n}\n\nvoid FDWatcher::close_fd()\n{\n    if (m_fd != -1)\n    {\n        close(m_fd);\n        m_fd = -1;\n    }\n}\n\nTimer::Timer(TimePoint date, Callback callback, EventMode mode)\n    : m_date{date}, m_mode(mode), m_callback{std::move(callback)}\n{\n    if (m_callback and EventManager::has_instance())\n        EventManager::instance().m_timers.push_back(this);\n}\n\nTimer::~Timer()\n{\n    if (m_callback and EventManager::has_instance())\n        unordered_erase(EventManager::instance().m_timers, this);\n}\n\nvoid Timer::run(EventMode mode)\n{\n    kak_assert(m_callback);\n    if (mode == m_mode)\n    {\n        m_date = TimePoint::max();\n        m_callback(*this);\n    }\n    else // try again a little later\n        m_date = Clock::now() + std::chrono::milliseconds{10};\n}\n\nEventManager::EventManager()\n{\n    FD_ZERO(&m_forced_fd);\n}\n\nEventManager::~EventManager()\n{\n    kak_assert(m_fd_watchers.empty());\n    kak_assert(m_timers.empty());\n}\n\nbool EventManager::handle_next_events(EventMode mode, sigset_t* sigmask, Optional<Nanoseconds> timeout)\n{\n    int max_fd = 0;\n    fd_set rfds, wfds, efds;\n    FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds);\n    for (auto& watcher : m_fd_watchers)\n    {\n        if (watcher->mode() == EventMode::Normal and mode == EventMode::Urgent)\n            continue;\n\n        const int fd = watcher->fd();\n        if (fd == -1)\n            continue;\n\n        max_fd = std::max(fd, max_fd);\n        auto events = watcher->events();\n        if (events & FdEvents::Read)\n            FD_SET(fd, &rfds);\n        if (events & FdEvents::Write)\n            FD_SET(fd, &wfds);\n        if (events & FdEvents::Except)\n            FD_SET(fd, &efds);\n    }\n\n    if (m_has_forced_fd)\n        timeout.reset();\n\n    if (not m_timers.empty())\n    {\n        auto next_date = (*std::min_element(\n            m_timers.begin(), m_timers.end(), [](Timer* lhs, Timer* rhs) {\n                return lhs->next_date() < rhs->next_date();\n        }))->next_date();\n\n        if (next_date != TimePoint::max())\n        {\n            auto remaining = std::max(Nanoseconds(0),\n                                      std::chrono::duration_cast<Nanoseconds>(next_date - Clock::now()));\n            timeout = timeout ? std::min(*timeout, remaining) : remaining;\n        }\n    }\n    auto ts = timeout.map([](auto nsecs) {\n        auto secs = std::chrono::duration_cast<std::chrono::seconds>(nsecs);\n        return timespec{(time_t)secs.count(), (long)(nsecs - secs).count()};\n    });\n    int res = pselect(max_fd + 1, &rfds, &wfds, &efds, ts ? &*ts : nullptr, sigmask);\n\n    // copy forced fds *after* select, so that signal handlers can write to\n    // m_forced_fd, interupt select, and directly be serviced.\n    m_has_forced_fd = false;\n    fd_set forced = m_forced_fd;\n    FD_ZERO(&m_forced_fd);\n\n    for (int fd = 0; fd < max_fd + 1; ++fd)\n    {\n        auto events =  FD_ISSET(fd, &forced) ? FdEvents::Read : FdEvents::None;\n        if (res > 0)\n            events |= (FD_ISSET(fd, &rfds) ? FdEvents::Read : FdEvents::None) |\n                      (FD_ISSET(fd, &wfds) ? FdEvents::Write : FdEvents::None) |\n                      (FD_ISSET(fd, &efds) ? FdEvents::Except : FdEvents::None);\n\n        if (events != FdEvents::None)\n        {\n            auto it = find_if(m_fd_watchers,\n                              [fd](const FDWatcher* w){return w->fd() == fd; });\n            if (it != m_fd_watchers.end())\n                (*it)->run(events, mode);\n        }\n    }\n\n    TimePoint now = Clock::now();\n    auto timers = m_timers; // copy timers in case m_timers gets mutated\n    for (auto& timer : timers)\n    {\n        if (contains(m_timers, timer) and timer->next_date() <= now)\n            timer->run(mode);\n    }\n\n    return res > 0;\n}\n\nvoid EventManager::force_signal(int fd)\n{\n    FD_SET(fd, &m_forced_fd);\n    m_has_forced_fd = true;\n}\n\nvoid EventManager::handle_urgent_events()\n{\n    if (has_instance())\n        instance().handle_next_events(EventMode::Urgent, nullptr, Nanoseconds{});\n}\n\n\nSignalHandler set_signal_handler(int signum, SignalHandler handler)\n{\n    struct sigaction new_action, old_action;\n\n    sigemptyset(&new_action.sa_mask);\n    new_action.sa_handler = handler;\n    new_action.sa_flags = SA_RESTART;\n    sigaction(signum, &new_action, &old_action);\n    return old_action.sa_handler;\n}\n\n}\n"
  },
  {
    "path": "src/event_manager.hh",
    "content": "#ifndef event_manager_hh_INCLUDED\n#define event_manager_hh_INCLUDED\n\n#include \"clock.hh\"\n#include \"meta.hh\"\n#include \"utils.hh\"\n#include \"optional.hh\"\n#include \"vector.hh\"\n#include \"function.hh\"\n\n#include <sys/select.h>\n#include <csignal>\n\nnamespace Kakoune\n{\n\nenum class EventMode\n{\n    Normal,\n    Urgent,\n};\n\nenum class FdEvents\n{\n    None = 0,\n    Read = 1 << 0,\n    Write = 1 << 1,\n    Except = 1 << 2,\n};\n\nconstexpr bool with_bit_ops(Meta::Type<FdEvents>) { return true; }\n\nclass FDWatcher\n{\npublic:\n    using Callback = Function<void (FDWatcher& watcher, FdEvents events, EventMode mode)>;\n    FDWatcher(int fd, FdEvents events, EventMode mode, Callback callback);\n    FDWatcher(const FDWatcher&) = delete;\n    FDWatcher& operator=(const FDWatcher&) = delete;\n    ~FDWatcher();\n\n    int fd() const { return m_fd; }\n    FdEvents events() const { return m_events; }\n    FdEvents& events() { return m_events; }\n    EventMode mode() const { return m_mode; }\n\n    void run(FdEvents events, EventMode mode);\n\n    void reset_fd(int fd) { m_fd = fd; }\n    void close_fd();\n    void disable() { m_fd = -1; }\n\nprivate:\n    int      m_fd;\n    FdEvents m_events;\n    EventMode m_mode;\n    Callback m_callback;\n};\n\nclass Timer\n{\npublic:\n    using Callback = Function<void (Timer& timer)>;\n\n    Timer(TimePoint date, Callback callback,\n          EventMode mode = EventMode::Normal);\n    Timer(const Timer&) = delete;\n    Timer& operator=(const Timer&) = delete;\n    ~Timer();\n\n    TimePoint next_date() const { return m_date; }\n    void set_next_date(TimePoint date) { m_date = date; }\n    void disable() { m_date = TimePoint::max(); }\n    void run(EventMode mode);\n\nprivate:\n    TimePoint m_date;\n    EventMode m_mode;\n    Callback  m_callback;\n};\n\n// The EventManager provides an interface to file descriptor\n// based event handling.\n//\n// The program main loop should call handle_next_events()\n// until it's time to quit.\nclass EventManager : public Singleton<EventManager>\n{\npublic:\n    using Nanoseconds = std::chrono::nanoseconds;\n\n    EventManager();\n    ~EventManager();\n\n    // blocks until next event if no timeout given\n    bool handle_next_events(EventMode mode, sigset_t* sigmask = nullptr,\n                            Optional<Nanoseconds> timeout = {});\n\n    // force the watchers associated with fd to be executed\n    // on next handle_next_events call.\n    void force_signal(int fd);\n\n    static void handle_urgent_events();\n\nprivate:\n    friend class FDWatcher;\n    friend class Timer;\n    Vector<FDWatcher*, MemoryDomain::Events> m_fd_watchers;\n    Vector<Timer*, MemoryDomain::Events>     m_timers;\n    fd_set m_forced_fd;\n    bool   m_has_forced_fd = false;\n\n    TimePoint m_last;\n};\n\nusing SignalHandler = void(*)(int);\n\nSignalHandler set_signal_handler(int signum, SignalHandler handler);\n\n}\n\n#endif // event_manager_hh_INCLUDED\n"
  },
  {
    "path": "src/exception.cc",
    "content": "#include \"exception.hh\"\n\n#include \"string.hh\"\n\n#include <typeinfo>\n\nnamespace Kakoune\n{\n\nStringView exception::what() const\n{\n    return typeid(*this).name();\n}\n\n}\n"
  },
  {
    "path": "src/exception.hh",
    "content": "#ifndef exception_hh_INCLUDED\n#define exception_hh_INCLUDED\n\n#include \"string.hh\"\n\nnamespace Kakoune\n{\n\nstruct exception\n{\n    virtual ~exception() = default;\n    virtual StringView what() const;\n};\n\nstruct runtime_error : exception\n{\n    runtime_error(String what)\n        : m_what(std::move(what)) {}\n\n    StringView what() const override { return m_what; }\n    void set_what(String what) { m_what = std::move(what); }\n\nprivate:\n    String m_what;\n};\n\nstruct failure : runtime_error\n{\n    using runtime_error::runtime_error;\n};\n\nstruct cancel : runtime_error\n{\n    cancel() : runtime_error(\"cancellation requested\") {}\n};\n\nstruct logic_error : exception\n{\n};\n\n}\n\n#endif // exception_hh_INCLUDED\n"
  },
  {
    "path": "src/face.hh",
    "content": "#ifndef face_hh_INCLUDED\n#define face_hh_INCLUDED\n\n#include \"color.hh\"\n#include \"flags.hh\"\n\nnamespace Kakoune\n{\n\nenum class Attribute : int\n{\n    Normal          = 0,\n    Underline       = 1 << 1,\n    CurlyUnderline  = 1 << 2,\n    DoubleUnderline = 1 << 3,\n    Reverse         = 1 << 4,\n    Blink           = 1 << 5,\n    Bold            = 1 << 6,\n    Dim             = 1 << 7,\n    Italic          = 1 << 8,\n    Strikethrough   = 1 << 9,\n    FinalFg         = 1 << 10,\n    FinalBg         = 1 << 11,\n    FinalAttr       = 1 << 12,\n    Final           = FinalFg | FinalBg | FinalAttr\n};\n\nconstexpr bool with_bit_ops(Meta::Type<Attribute>) { return true; }\n\nstruct Face\n{\n    Color fg = Color::Default;\n    Color bg = Color::Default;\n    Attribute attributes = Attribute::Normal;\n    Color underline = Color::Default;\n\n    friend constexpr bool operator==(const Face& lhs, const Face& rhs) = default;\n\n    friend constexpr size_t hash_value(const Face& val)\n    {\n        return hash_values(val.fg, val.bg, val.underline, val.attributes);\n    }\n};\n\ninline Face merge_faces(const Face& base, const Face& face)\n{\n    auto alpha_blend = [](Color base, Color color) {\n        auto blend = [&](unsigned char Color::*field) {\n            int blended = (base.*field * (255 - color.a) + color.*field * color.a) / 255;\n            return static_cast<unsigned char>(blended <= 255 ? blended : 255);\n        };\n        int alpha = color.a + base.a * (255 - color.a) / 255;\n        return Color{blend(&Color::r), blend(&Color::g), blend(&Color::b),\n                     static_cast<unsigned char>(alpha <= 255 ? alpha : 255)};\n    };\n\n    auto choose = [&](Color Face::*color, Attribute final_attr) {\n        if (face.attributes & final_attr)\n            return face.*color;\n        if (base.attributes & final_attr)\n            return base.*color;\n        if (face.*color == Color::Default)\n            return base.*color;\n        if ((base.*color).isRGB() and (face.*color).isRGB() and (face.*color).a != 255)\n            return alpha_blend(base.*color, face.*color);\n        return face.*color;\n    };\n\n    return Face{ choose(&Face::fg, Attribute::FinalFg),\n                 choose(&Face::bg, Attribute::FinalBg),\n                 face.attributes & Attribute::FinalAttr ? face.attributes | Attribute{base.attributes & Attribute::Final} :\n                 base.attributes & Attribute::FinalAttr ? base.attributes :\n                 face.attributes | base.attributes,\n                 choose(&Face::underline, Attribute{0}) };\n}\n\n}\n\n#endif // face_hh_INCLUDED\n"
  },
  {
    "path": "src/face_registry.cc",
    "content": "#include \"face_registry.hh\"\n\n#include \"exception.hh\"\n#include \"ranges.hh\"\n#include \"format.hh\"\n\nnamespace Kakoune\n{\n\nFaceSpec parse_face(StringView facedesc)\n{\n    constexpr StringView invalid_face_error = \"invalid face description, expected [<fg>][,<bg>[,<underline>]][+<attr>][@base] or just [base]\";\n    if (all_of(facedesc, [](char c){ return is_word(c); }) and not is_color_name(facedesc))\n        return {Face{}, facedesc.str()};\n\n    auto bg_it = find(facedesc, ',');\n    auto underline_it = bg_it == facedesc.end() ? bg_it : std::find(bg_it+1, facedesc.end(), ',');\n    auto attr_it = find(facedesc, '+');\n    auto base_it = find(facedesc, '@');\n    if (bg_it != facedesc.end()\n        and (attr_it < bg_it or (bg_it + 1) == facedesc.end()))\n        throw runtime_error(invalid_face_error.str());\n    if (attr_it != facedesc.end()\n        and (attr_it + 1) == facedesc.end())\n        throw runtime_error(invalid_face_error.str());\n\n    auto colors_end = std::min(attr_it, base_it);\n    if (underline_it != facedesc.end()\n        and underline_it > colors_end)\n        throw runtime_error(invalid_face_error.str());\n\n    auto parse_color = [](StringView spec) {\n        return spec.empty() ? Color::Default : str_to_color(spec);\n    };\n\n    FaceSpec spec;\n    auto& face = spec.face;\n    face.fg = parse_color({facedesc.begin(), std::min(bg_it, colors_end)});\n    if (bg_it != facedesc.end())\n    {\n        face.bg = parse_color({bg_it+1, std::min(underline_it, colors_end)});\n        if (underline_it != facedesc.end())\n            face.underline = parse_color({underline_it+1, colors_end});\n    }\n    if (attr_it != facedesc.end())\n    {\n        for (++attr_it; attr_it != base_it; ++attr_it)\n        {\n            switch (*attr_it)\n            {\n                case 'u': face.attributes |= Attribute::Underline; break;\n                case 'c': face.attributes |= Attribute::CurlyUnderline; break;\n                case 'U': face.attributes |= Attribute::DoubleUnderline; break;\n                case 'r': face.attributes |= Attribute::Reverse; break;\n                case 'b': face.attributes |= Attribute::Bold; break;\n                case 'B': face.attributes |= Attribute::Blink; break;\n                case 'd': face.attributes |= Attribute::Dim; break;\n                case 'i': face.attributes |= Attribute::Italic; break;\n                case 's': face.attributes |= Attribute::Strikethrough; break;\n                case 'f': face.attributes |= Attribute::FinalFg; break;\n                case 'g': face.attributes |= Attribute::FinalBg; break;\n                case 'a': face.attributes |= Attribute::FinalAttr; break;\n                case 'F': face.attributes |= Attribute::Final; break;\n                default: throw runtime_error(format(\"no such face attribute: '{}'\", StringView{*attr_it}));\n            }\n        }\n    }\n    if (base_it != facedesc.end())\n        spec.base = String{base_it+1, facedesc.end()};\n    return spec;\n}\n\nString to_string(Attribute attributes)\n{\n    if (attributes == Attribute::Normal)\n        return \"\";\n\n    struct Attr { Attribute attr; StringView name; }\n    attrs[] {\n        { Attribute::Underline, \"u\" },\n        { Attribute::CurlyUnderline, \"c\" },\n        { Attribute::DoubleUnderline, \"U\" },\n        { Attribute::Reverse, \"r\" },\n        { Attribute::Blink, \"B\" },\n        { Attribute::Bold, \"b\" },\n        { Attribute::Dim, \"d\" },\n        { Attribute::Italic, \"i\" },\n        { Attribute::Strikethrough, \"s\" },\n        { Attribute::Final, \"F\" },\n        { Attribute::FinalFg, \"f\" },\n        { Attribute::FinalBg, \"g\" },\n        { Attribute::FinalAttr, \"a\" },\n    };\n\n    auto filteredAttrs = attrs |\n                         filter([&](const Attr& a) {\n                             if ((attributes & a.attr) != a.attr)\n                                 return false;\n                             attributes &= ~a.attr;\n                             return true;\n                         }) | transform([](const Attr& a) { return a.name; });\n\n    return accumulate(filteredAttrs, \"+\"_str, std::plus<>{});\n}\n\nString to_string(Face face)\n{\n    return format(\"{},{},{}{}\", face.fg, face.bg, face.underline, face.attributes);\n}\n\nFace FaceRegistry::operator[](StringView facedesc) const\n{\n    return resolve_spec(parse_face(facedesc));\n}\n\nFace FaceRegistry::operator[](const FaceSpec& spec) const\n{\n    return resolve_spec(spec);\n}\n\nFace FaceRegistry::resolve_spec(const FaceSpec& spec) const\n{\n    if (spec.base.empty())\n        return spec.face;\n\n    StringView base = spec.base;\n    Face face = spec.face;\n    for (auto* reg = this; reg != nullptr; reg = reg->m_parent.get())\n    {\n        auto it = reg->m_faces.find(base);\n        if (it == reg->m_faces.end())\n            continue;\n\n        if (it->value.base.empty())\n            return merge_faces(it->value.face, face);\n        if (it->value.base != it->key)\n            return merge_faces(reg->resolve_spec(it->value), face);\n        else\n        {\n            face = merge_faces(it->value.face, face);\n            base = it->value.base;\n        }\n    }\n    return face;\n}\n\nvoid FaceRegistry::add_face(StringView name, StringView facedesc, bool override)\n{\n    if (not override and m_faces.find(name) != m_faces.end())\n        throw runtime_error(format(\"face '{}' already defined\", name));\n\n    if (name.empty() or is_color_name(name) or\n        any_of(name, [](char c){ return not is_word(c); }))\n        throw runtime_error(format(\"invalid face name: '{}'\", name));\n\n    FaceSpec spec = parse_face(facedesc);\n    auto it = m_faces.find(spec.base);\n    if (spec.base == name and it != m_faces.end())\n    {\n        it->value.face = merge_faces(it->value.face, spec.face);\n        it->value.base = spec.base;\n        return;\n    }\n\n    while (it != m_faces.end() and not it->value.base.empty())\n    {\n        if (it->value.base == name)\n            throw runtime_error(\"face cycle detected\");\n        it = m_faces.find(it->value.base);\n    }\n    m_faces[name] = std::move(spec);\n}\n\nvoid FaceRegistry::remove_face(StringView name)\n{\n    m_faces.remove(name);\n}\n\nFaceRegistry::FaceRegistry()\n    : m_faces{\n        { \"Default\", {Face{ Color::Default, Color::Default }} },\n        { \"PrimarySelection\", {Face{ Color::White, Color::Blue }} },\n        { \"SecondarySelection\", {Face{ Color::Black, Color::Blue }} },\n        { \"PrimaryCursor\", {Face{ Color::Black, Color::White }} },\n        { \"SecondaryCursor\", {Face{ Color::Black, Color::White }} },\n        { \"PrimaryCursorEol\", {Face{ Color::Black, Color::Cyan }} },\n        { \"SecondaryCursorEol\", {Face{ Color::Black, Color::Cyan }} },\n        { \"LineNumbers\", {Face{ Color::Default, Color::Default }} },\n        { \"LineNumberCursor\", {Face{ Color::Default, Color::Default, Attribute::Reverse }} },\n        { \"LineNumbersWrapped\", {Face{ Color::Default, Color::Default, Attribute::Italic }} },\n        { \"WrapMarker\", {Face{ Color::Blue, Color::Default }} },\n        { \"MenuForeground\", {Face{ Color::White, Color::Blue }} },\n        { \"MenuBackground\", {Face{ Color::Blue, Color::White }} },\n        { \"MenuInfo\", {Face{ Color::Cyan, Color::Default }} },\n        { \"Information\", {Face{ Color::Black, Color::Yellow }} },\n        { \"InlineInformation\", {Face{}, \"Information\"} },\n        { \"Error\", {Face{ Color::Black, Color::Red }} },\n        { \"DiagnosticError\", {Face{ Color::Red, Color::Default }} },\n        { \"DiagnosticWarning\", {Face{ Color::Yellow, Color::Default }} },\n        { \"StatusLine\", {Face{ Color::Cyan, Color::Default }} },\n        { \"StatusLineMode\", {Face{ Color::Yellow, Color::Default }} },\n        { \"StatusLineInfo\", {Face{ Color::Blue, Color::Default }} },\n        { \"StatusLineValue\", {Face{ Color::Green, Color::Default }} },\n        { \"StatusCursor\", {Face{ Color::Black, Color::Cyan }} },\n        { \"Prompt\", {Face{ Color::Yellow, Color::Default }} },\n        { \"MatchingChar\", {Face{ Color::Default, Color::Default, Attribute::Bold }} },\n        { \"BufferPadding\", {Face{ Color::Blue, Color::Default }} },\n        { \"Whitespace\", {Face{ Color::Default, Color::Default, Attribute::FinalFg }} },\n        { \"WhitespaceIndent\", {Face{}, \"Whitespace\"} },\n      }\n{}\n\n}\n"
  },
  {
    "path": "src/face_registry.hh",
    "content": "#ifndef face_registry_hh_INCLUDED\n#define face_registry_hh_INCLUDED\n\n#include \"face.hh\"\n#include \"hash_map.hh\"\n#include \"ranges.hh\"\n#include \"string.hh\"\n#include \"safe_ptr.hh\"\n\nnamespace Kakoune\n{\n\nstruct FaceSpec\n{\n    Face face = {};\n    String base = {};\n\n    friend bool operator==(const FaceSpec&, const FaceSpec&) = default;\n};\n\nclass FaceRegistry : public SafeCountable\n{\npublic:\n    FaceRegistry(FaceRegistry& parent) : SafeCountable{}, m_parent(&parent) {}\n\n    void reparent(FaceRegistry& parent) { m_parent = &parent; }\n\n    Face operator[](StringView facedesc) const;\n    Face operator[](const FaceSpec& facespec) const;\n    void add_face(StringView name, StringView facedesc, bool override = false);\n    void remove_face(StringView name);\n\n    using FaceMap = HashMap<String, FaceSpec, MemoryDomain::Faces>;\n\n    auto flatten_faces() const\n    {\n        auto merge = [](auto&& first, const FaceMap& second) {\n            return concatenated(std::forward<decltype(first)>(first)\n                                | filter([&second](auto& i) { return not second.contains(i.key); }),\n                                second);\n        };\n        static const FaceMap empty;\n        auto& parent = m_parent ? m_parent->m_faces : empty;\n        auto& grand_parent = (m_parent and m_parent->m_parent) ? m_parent->m_parent->m_faces : empty;\n        return merge(merge(grand_parent, parent), m_faces);\n    }\n\nprivate:\n    Face resolve_spec(const FaceSpec& spec) const;\n\n    friend class Scope;\n    FaceRegistry();\n\n    SafePtr<FaceRegistry> m_parent;\n    FaceMap m_faces;\n};\n\nFaceSpec parse_face(StringView facedesc);\nString to_string(Face face);\n\n}\n\n#endif // face_registry_hh_INCLUDED\n"
  },
  {
    "path": "src/file.cc",
    "content": "#include \"file.hh\"\n\n#include \"assert.hh\"\n#include \"event_manager.hh\"\n#include \"string.hh\"\n#include \"string_utils.hh\"\n#include \"format.hh\"\n#include \"ranges.hh\"\n\n#include <limits>\n#include <cerrno>\n#include <cstdlib>\n#include <cstdio>\n#include <cstring>\n#include <dirent.h>\n#include <fcntl.h>\n#include <pwd.h>\n#include <sys/mman.h>\n#include <sys/select.h>\n#include <unistd.h>\n\n#if defined(__FreeBSD__) or defined(__NetBSD__)\n#include <sys/sysctl.h>\n#endif\n\n#if defined(__APPLE__)\n#include <mach-o/dyld.h>\n#define st_mtim st_mtimespec\n#endif\n\n#if defined(__HAIKU__)\n#include <app/Application.h>\n#include <app/Roster.h>\n#include <storage/Path.h>\n#endif\n\nnamespace Kakoune\n{\n\nfile_access_error::file_access_error(StringView filename,\n                                     StringView error_desc)\n    : runtime_error(format(\"{}: {}\", filename, error_desc)) {}\n\nfile_access_error::file_access_error(int fd, StringView error_desc)\n    : runtime_error(format(\"fd {}: {}\", fd, error_desc)) {}\n\nString parse_filename(StringView filename, StringView buf_dir)\n{\n    auto prefix = filename.substr(0_byte, 2_byte);\n    if (prefix == \"~\" or prefix == \"~/\")\n        return homedir() + filename.substr(1_byte);\n    if ((prefix == \"%\" or prefix == \"%/\") and not buf_dir.empty())\n        return buf_dir + filename.substr(1_byte);\n    return filename.str();\n}\n\nstd::pair<StringView, StringView> split_path(StringView path)\n{\n    auto it = find(path | reverse(), '/');\n    if (it == path.rend())\n        return { {}, path };\n    const char* slash = it.base()-1;\n    return { {path.begin(), slash+1}, {slash+1, path.end()} };\n}\n\nString real_path(StringView filename)\n{\n    if (filename.empty())\n        return {};\n\n    char buffer[PATH_MAX+1];\n\n    StringView existing = filename;\n    StringView non_existing{};\n\n    while (true)\n    {\n        if (char* res = realpath(existing.zstr(), buffer))\n        {\n            if (non_existing.empty())\n                return res;\n\n            StringView dir = res;\n            while (not dir.empty() and dir.back() == '/')\n                dir = dir.substr(0_byte, dir.length()-1_byte);\n            return format(\"{}/{}\", dir, non_existing);\n        }\n\n        auto it = find(existing.rbegin() + 1, existing.rend(), '/');\n        if (it == existing.rend())\n        {\n            char cwd[1024];\n            return format(\"{}/{}\", getcwd(cwd, 1024), filename);\n        }\n\n        existing = StringView{existing.begin(), it.base()};\n        non_existing = StringView{it.base(), filename.end()};\n    }\n}\n\nString compact_path(StringView filename)\n{\n    String real_filename = real_path(filename);\n\n    char cwd[1024];\n    if (!::getcwd(cwd, 1024))\n        throw runtime_error(format(\"unable to get the current working directory (errno: {})\", ::strerror(errno)));\n\n    String real_cwd = real_path(cwd) + \"/\";\n    if (prefix_match(real_filename, real_cwd))\n        return real_filename.substr(real_cwd.length()).str();\n\n    StringView home = homedir();\n    while (not home.empty() and home.back() == '/')\n        home = home.substr(0_byte, home.length()-1_byte);\n\n    if (not home.empty())\n    {\n        ByteCount home_len = home.length();\n        if (real_filename.substr(0, home_len) == home)\n            return \"~\" + real_filename.substr(home_len);\n    }\n\n    return filename.str();\n}\n\nStringView tmpdir()\n{\n    StringView tmpdir = getenv(\"TMPDIR\");\n    if (not tmpdir.empty())\n        return tmpdir.back() == '/' ? tmpdir.substr(0_byte, tmpdir.length()-1)\n                                    : tmpdir;\n    return \"/tmp\";\n}\n\nStringView homedir()\n{\n    StringView home = getenv(\"HOME\");\n    if (home.empty())\n        return getpwuid(geteuid())->pw_dir;\n    return home;\n}\n\nbool fd_readable(int fd)\n{\n    kak_assert(fd >= 0);\n    fd_set  rfds;\n    FD_ZERO(&rfds);\n    FD_SET(fd, &rfds);\n\n    timeval tv{0,0};\n    return select(fd+1, &rfds, nullptr, nullptr, &tv) == 1;\n}\n\nbool fd_writable(int fd)\n{\n    kak_assert(fd >= 0);\n    fd_set  wfds;\n    FD_ZERO(&wfds);\n    FD_SET(fd, &wfds);\n\n    timeval tv{0,0};\n    return select(fd+1, nullptr, &wfds, nullptr, &tv) == 1;\n}\n\nString read_fd(int fd, bool text)\n{\n    String content;\n    constexpr size_t bufsize = 256;\n    char buf[bufsize];\n    while (ssize_t size = read(fd, buf, bufsize))\n    {\n        if (size == -1)\n            throw file_access_error{fd, strerror(errno)};\n\n        if  (text)\n        {\n            for (StringView data{buf, buf + size}; not data.empty();)\n            {\n               auto it = find(data, '\\r');\n               content += StringView{data.begin(), it};\n               data = StringView{(it != data.end()) ? it+1 : it, data.end()};\n            }\n        }\n        else\n            content += StringView{buf, buf + size};\n    }\n    return content;\n}\n\nString read_file(StringView filename, bool text)\n{\n    int fd = open(filename.zstr(), O_RDONLY);\n    if (fd == -1)\n        throw file_access_error(filename, strerror(errno));\n\n    auto close_fd = OnScopeEnd([fd]{ close(fd); });\n    return read_fd(fd, text);\n}\n\nMappedFile::MappedFile(StringView filename)\n    : data{nullptr}\n{\n    int fd = open(filename.zstr(), O_RDONLY | O_NONBLOCK);\n    if (fd == -1)\n        throw file_access_error(filename, strerror(errno));\n    auto close_fd = OnScopeEnd([&] { close(fd); });\n\n    fstat(fd, &st);\n    if (S_ISDIR(st.st_mode))\n        throw file_access_error(filename, \"is a directory\");\n\n    if (st.st_size == 0)\n        return;\n\n    data = (const char*)mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);\n    if (data == MAP_FAILED)\n        throw file_access_error{filename, strerror(errno)};\n}\n\nMappedFile::~MappedFile()\n{\n    if (data != nullptr)\n        munmap((void*)data, st.st_size);\n}\n\nMappedFile::operator StringView() const\n{\n    if (st.st_size > std::numeric_limits<int>::max())\n        throw runtime_error(\"file is too big\");\n    return { data, (int)st.st_size };\n}\n\nbool file_exists(StringView filename)\n{\n    struct stat st;\n    return stat(filename.zstr(), &st) == 0;\n}\n\nbool regular_file_exists(StringView filename)\n{\n    struct stat st;\n    return stat(filename.zstr(), &st) == 0 and\n           (st.st_mode & S_IFMT) == S_IFREG;\n}\n\ntemplate<bool atomic>\nvoid write(int fd, StringView data)\n{\n    const char* ptr = data.data();\n    ssize_t count   = (int)data.length();\n\n    int flags = fcntl(fd, F_GETFL, 0);\n    if (not atomic and EventManager::has_instance())\n        fcntl(fd, F_SETFL, flags | O_NONBLOCK);\n    auto restore_flags = OnScopeEnd([&] { fcntl(fd, F_SETFL, flags); });\n\n    while (count)\n    {\n        if (ssize_t written = ::write(fd, ptr, count); written != -1)\n        {\n            ptr += written;\n            count -= written;\n        }\n        else if (errno == EAGAIN and not atomic and EventManager::has_instance())\n            EventManager::instance().handle_next_events(EventMode::Urgent, nullptr, std::chrono::nanoseconds{});\n        else\n            throw file_access_error(fd, strerror(errno));\n    }\n}\ntemplate void write<true>(int fd, StringView data);\ntemplate void write<false>(int fd, StringView data);\n\nint create_file(const char* filename)\n{\n    int fd;\n    const int flags = O_CREAT | O_WRONLY | O_TRUNC | (EventManager::has_instance() ? O_NONBLOCK : 0);\n    while ((fd = open(filename, flags, 0644)) == -1)\n    {\n        if (errno == ENXIO and EventManager::has_instance()) // trying to open a FIFO with no readers yet\n            EventManager::instance().handle_next_events(EventMode::Urgent, nullptr,\n                                                        std::chrono::nanoseconds{1'000'000});\n        else\n            return -1;\n    }\n    return fd;\n}\n\nvoid write_to_file(StringView filename, StringView data)\n{\n    int fd = create_file(filename.zstr());\n    if (fd == -1)\n        throw file_access_error(filename, strerror(errno));\n    auto close_fd = OnScopeEnd([fd]{ close(fd); });\n    write(fd, data);\n}\n\nint open_temp_file(StringView filename, char (&buffer)[PATH_MAX])\n{\n    String path = real_path(filename);\n    auto [dir,file] = split_path(path);\n\n    if (dir.empty())\n        format_to(buffer, \".{}.kak.XXXXXX\", file);\n    else\n        format_to(buffer, \"{}/.{}.kak.XXXXXX\", dir, file);\n\n    return mkstemp(buffer);\n}\n\nint open_temp_file(StringView filename)\n{\n    char buffer[PATH_MAX];\n    return open_temp_file(filename, buffer);\n}\n\nString find_file(StringView filename, StringView buf_dir, ConstArrayView<String> paths)\n{\n    struct stat buf;\n    if (filename.substr(0_byte, 1_byte) == \"/\")\n    {\n        if (stat(filename.zstr(), &buf) == 0 and S_ISREG(buf.st_mode))\n            return filename.str();\n         return \"\";\n    }\n    if (filename.substr(0_byte, 2_byte) == \"~/\")\n    {\n        String candidate = homedir() + filename.substr(1_byte);\n        if (stat(candidate.c_str(), &buf) == 0 and S_ISREG(buf.st_mode))\n            return candidate;\n        return \"\";\n    }\n\n    for (auto candidate : paths | transform([&](StringView s) { return parse_filename(s, buf_dir); }))\n    {\n        if (not candidate.empty() and candidate.back() != '/')\n            candidate += '/';\n        candidate += filename;\n        if (stat(candidate.c_str(), &buf) == 0 and S_ISREG(buf.st_mode))\n            return candidate;\n    }\n    return \"\";\n}\n\nvoid make_directory(StringView dir, mode_t mode)\n{\n    auto it = dir.begin(), end = dir.end();\n    while(it != end)\n    {\n        it = std::find(it+1, end, '/');\n        struct stat st;\n        StringView dirname{dir.begin(), it};\n        if (stat(dirname.zstr(), &st) == 0)\n        {\n            if (not S_ISDIR(st.st_mode))\n                throw runtime_error(format(\"cannot make directory, '{}' exists but is not a directory\", dirname));\n        }\n        else\n        {\n            auto old_mask = umask(0);\n            auto restore_mask = OnScopeEnd([old_mask]() { umask(old_mask); });\n\n            if (mkdir(dirname.zstr(), mode) != 0)\n                throw runtime_error(format(\"mkdir failed for directory '{}' errno {}\", dirname, errno));\n        }\n    }\n}\n\nvoid list_files(StringView dirname, FunctionRef<void (StringView, const struct stat&)> callback)\n{\n    char buffer[PATH_MAX+1];\n    format_to(buffer, \"{}\", dirname);\n    DIR* dir = opendir(dirname.empty() ? \"./\" : buffer);\n    if (not dir)\n        return;\n\n    auto close_dir = OnScopeEnd([dir]{ closedir(dir); });\n\n    while (dirent* entry = readdir(dir))\n    {\n        StringView filename = entry->d_name;\n        if (filename.empty())\n            continue;\n\n        struct stat st;\n        auto fmt_str = (dirname.empty() or dirname.back() == '/') ? \"{}{}\" : \"{}/{}\";\n        format_to(buffer, fmt_str, dirname, filename);\n        if (stat(buffer, &st) != 0)\n            continue;\n\n        if (S_ISDIR(st.st_mode))\n            filename = format_to(buffer, \"{}/\", filename);\n        callback(filename, st);\n    }\n}\n\ntimespec get_fs_timestamp(StringView filename)\n{\n    struct stat st;\n    if (stat(filename.zstr(), &st) != 0)\n        return InvalidTime;\n    return st.st_mtim;\n}\n\nFsStatus get_fs_status(StringView filename)\n{\n    MappedFile fd{filename};\n\n    return {fd.st.st_mtim, fd.st.st_size, murmur3(fd.data, fd.st.st_size)};\n}\n\nString get_kak_binary_path()\n{\n    char buffer[2048];\n#if defined(__linux__) or defined(__CYGWIN__) or defined(__gnu_hurd__)\n    ssize_t res = readlink(\"/proc/self/exe\", buffer, 2048);\n    if (res != -1 && res < 2048) {\n        buffer[res] = '\\0';\n        return buffer;\n    }\n#elif defined(__FreeBSD__) or defined(__NetBSD__)\n#if defined(__FreeBSD__)\n    int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};\n#elif defined(__NetBSD__)\n    int mib[] = {CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME};\n#endif\n    size_t res = sizeof(buffer);\n    if (sysctl(mib, 4, buffer, &res, NULL, 0) != -1)\n        return buffer;\n#elif defined(__APPLE__)\n    uint32_t bufsize = 2048;\n    char* canonical_path = NULL;\n    if (_NSGetExecutablePath(buffer, &bufsize) != -1)\n        canonical_path = realpath(buffer, nullptr);\n    if (canonical_path) {\n        String path = canonical_path;\n        free(canonical_path);\n        return path;\n    }\n#elif defined(__HAIKU__)\n    BApplication app(\"application/x-vnd.kakoune\");\n    app_info info;\n    if (app.GetAppInfo(&info) == B_OK) {\n        BPath path(&info.ref);\n        return path.Path();\n    }\n#elif defined(__DragonFly__)\n    ssize_t res = readlink(\"/proc/curproc/file\", buffer, 2048);\n    if (res != -1 && res < 2048) {\n        buffer[res] = '\\0';\n        return buffer;\n    }\n#elif defined(__OpenBSD__)\n    (void)buffer;\n    return KAK_BIN_PATH;\n#elif defined(__sun__)\n    ssize_t res = readlink(\"/proc/self/path/a.out\", buffer, 2048);\n    if (res != -1 && res < 2048) {\n        buffer[res] = '\\0';\n        return buffer;\n    }\n#else\n# error \"finding executable path is not implemented on this platform\"\n#endif\n    throw runtime_error(\"unable to get the executable path\");\n}\n\n}\n"
  },
  {
    "path": "src/file.hh",
    "content": "#ifndef file_hh_INCLUDED\n#define file_hh_INCLUDED\n\n#include \"array.hh\"\n#include \"array_view.hh\"\n#include \"enum.hh\"\n#include \"exception.hh\"\n#include \"meta.hh\"\n#include \"string.hh\"\n#include \"units.hh\"\n#include \"utils.hh\"\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <cstring>\n#include <exception>\n\nnamespace Kakoune\n{\n\nstruct file_access_error : runtime_error\n{\n    file_access_error(StringView filename, StringView error_desc);\n    file_access_error(int fd, StringView error_desc);\n};\n\n// parse ~/ and %/ in filename and returns the translated filename\nString parse_filename(StringView filename, StringView buf_dir = {});\n\nString real_path(StringView filename);\nString compact_path(StringView filename);\n\nStringView tmpdir();\nStringView homedir();\n\n// returns pair { directory, filename }\nstd::pair<StringView, StringView> split_path(StringView path);\n\nString get_kak_binary_path();\n\nbool fd_readable(int fd);\nbool fd_writable(int fd);\nString read_fd(int fd, bool text = false);\nString read_file(StringView filename, bool text = false);\ntemplate<bool force_blocking = false>\nvoid write(int fd, StringView data);\nvoid write_to_file(StringView filename, StringView data);\nint create_file(const char* filename);\nint open_temp_file(StringView filename);\nint open_temp_file(StringView filename, char (&buffer)[PATH_MAX]);\n\nstruct MappedFile\n{\n    MappedFile(StringView filename);\n    ~MappedFile();\n\n    operator StringView() const;\n\n    const char* data;\n    struct stat st {};\n};\n\nenum class WriteMethod\n{\n    Overwrite,\n    Replace\n};\nconstexpr auto enum_desc(Meta::Type<WriteMethod>)\n{\n    return make_array<EnumDesc<WriteMethod>>({\n        { WriteMethod::Overwrite, \"overwrite\" },\n        { WriteMethod::Replace, \"replace\" },\n    });\n}\n\nString find_file(StringView filename, StringView buf_dir, ConstArrayView<String> paths);\nbool file_exists(StringView filename);\nbool regular_file_exists(StringView filename);\n\nvoid list_files(StringView directory, FunctionRef<void (StringView, const struct stat&)> callback);\n\nvoid make_directory(StringView dir, mode_t mode);\n\nconstexpr timespec InvalidTime = { -1, -1 };\n\nstruct FsStatus\n{\n    timespec timestamp;\n    ByteCount file_size;\n    size_t hash;\n};\n\ntimespec get_fs_timestamp(StringView filename);\nFsStatus get_fs_status(StringView filename);\n\nconstexpr bool operator==(const timespec& lhs, const timespec& rhs)\n{\n    return lhs.tv_sec == rhs.tv_sec and lhs.tv_nsec == rhs.tv_nsec;\n}\n\ntemplate<bool atomic, int buffer_size = 4096>\nstruct BufferedWriter\n{\n    BufferedWriter(int fd)\n      : m_fd{fd}, m_exception_count{std::uncaught_exceptions()} {}\n\n    ~BufferedWriter() noexcept(false)\n    {\n        if (m_pos != 0 and m_exception_count == std::uncaught_exceptions())\n            flush();\n    }\n\n    void write(StringView data)\n    {\n        while (not data.empty())\n        {\n            const ByteCount length = data.length();\n            const ByteCount write_len = clamp(length, ByteCount{0}, size - m_pos);\n            memcpy(m_buffer + m_pos, data.data(), (int)write_len);\n            m_pos += write_len;\n            if (m_pos == size)\n                flush();\n            data = data.substr(write_len);\n        }\n    }\n\n    void flush()\n    {\n        Kakoune::write<atomic>(m_fd, {m_buffer, m_pos});\n        m_pos = 0;\n    }\n\nprivate:\n    static constexpr ByteCount size = buffer_size;\n    int m_fd;\n    int m_exception_count;\n    ByteCount m_pos = 0;\n    char m_buffer[(int)size];\n};\n\n}\n\n#endif // file_hh_INCLUDED\n"
  },
  {
    "path": "src/flags.hh",
    "content": "#ifndef flags_hh_INCLUDED\n#define flags_hh_INCLUDED\n\n#include <type_traits>\n\n#include \"meta.hh\"\n\nnamespace Kakoune\n{\n\ntemplate<typename Flags>\nconstexpr bool with_bit_ops(Meta::Type<Flags>) { return false; }\n\ntemplate<typename Flags>\nconcept WithBitOps = with_bit_ops(Meta::Type<Flags>{});\n\ntemplate<typename Flags>\nusing UnderlyingType = std::underlying_type_t<Flags>;\n\ntemplate<WithBitOps Flags>\nconstexpr Flags operator|(Flags lhs, Flags rhs)\n{\n    return (Flags)((UnderlyingType<Flags>) lhs | (UnderlyingType<Flags>) rhs);\n}\n\ntemplate<WithBitOps Flags>\nconstexpr Flags& operator|=(Flags& lhs, Flags rhs)\n{\n    (UnderlyingType<Flags>&) lhs |= (UnderlyingType<Flags>) rhs;\n    return lhs;\n}\n\ntemplate<typename Flags>\nstruct TestableFlags\n{\n    Flags value;\n    constexpr operator bool() const { return (UnderlyingType<Flags>)value; }\n    constexpr operator Flags() const { return value; }\n    constexpr operator UnderlyingType<Flags>() const { return (UnderlyingType<Flags>)value; }\n\n    constexpr bool operator==(const TestableFlags<Flags>& other) const = default;\n};\n\ntemplate<WithBitOps Flags>\nconstexpr TestableFlags<Flags> operator&(Flags lhs, Flags rhs)\n{\n    return { (Flags)((UnderlyingType<Flags>) lhs & (UnderlyingType<Flags>) rhs) };\n}\n\ntemplate<WithBitOps Flags>\nconstexpr Flags& operator&=(Flags& lhs, Flags rhs)\n{\n    (UnderlyingType<Flags>&) lhs &= (UnderlyingType<Flags>) rhs;\n    return lhs;\n}\n\ntemplate<WithBitOps Flags>\nconstexpr Flags operator~(Flags lhs)\n{\n    return (Flags)(~(UnderlyingType<Flags>)lhs);\n}\n\ntemplate<WithBitOps Flags>\nconstexpr Flags operator^(Flags lhs, Flags rhs)\n{\n    return (Flags)((UnderlyingType<Flags>) lhs ^ (UnderlyingType<Flags>) rhs);\n}\n\ntemplate<WithBitOps Flags>\nconstexpr Flags& operator^=(Flags& lhs, Flags rhs)\n{\n    (UnderlyingType<Flags>&) lhs ^= (UnderlyingType<Flags>) rhs;\n    return lhs;\n}\n\n}\n\n#endif // flags_hh_INCLUDED\n"
  },
  {
    "path": "src/format.cc",
    "content": "#include \"format.hh\"\n\n#include \"exception.hh\"\n#include \"string_utils.hh\"\n\n#include <algorithm>\n#include <charconv>\n#include <cstdio>\n\nnamespace Kakoune\n{\n\n\ntemplate<size_t N>\nInplaceString<N> to_string_impl(auto val, auto format)\n{\n    InplaceString<N> res;\n    auto [end, errc] = std::to_chars(res.m_data, res.m_data + N, val, format);\n    if (errc != std::errc{})\n        throw runtime_error(\"to_string error\");\n    res.m_length = end - res.m_data;\n    *end = '\\0';\n    return res;\n}\n\ntemplate<size_t N>\nInplaceString<N> to_string_impl(auto val)\n{\n    return to_string_impl<N>(val, 10);\n}\n\nInplaceString<15> to_string(int val)\n{\n    return to_string_impl<15>(val);\n}\n\nInplaceString<15> to_string(unsigned val)\n{\n    return to_string_impl<15>(val);\n}\n\nInplaceString<23> to_string(long int val)\n{\n    return to_string_impl<23>(val);\n}\n\nInplaceString<23> to_string(long long int val)\n{\n    return to_string_impl<23>(val);\n}\n\nInplaceString<23> to_string(unsigned long val)\n{\n    return to_string_impl<23>(val);\n}\n\nInplaceString<23> to_string(Hex val)\n{\n    return to_string_impl<23>(val.val, 16);\n}\n\nInplaceString<23> to_string(Grouped val)\n{\n    auto ungrouped = to_string_impl<23>(val.val);\n\n    InplaceString<23> res;\n    for (int pos = 0, len = ungrouped.m_length; pos != len; ++pos)\n    {\n        if (res.m_length and ((len - pos) % 3) == 0)\n            res.m_data[res.m_length++] = ',';\n        res.m_data[res.m_length++] = ungrouped.m_data[pos];\n    }\n    return res;\n}\n\nInplaceString<23> to_string(float val)\n{\n#if defined(__cpp_lib_to_chars)\n    return to_string_impl<23>(val, std::chars_format::general);\n#else\n    InplaceString<23> res;\n    res.m_length = snprintf(res.m_data, 23, \"%f\", val);\n    return res;\n#endif\n}\n\nInplaceString<7> to_string(Codepoint c)\n{\n    InplaceString<7> res;\n    char* ptr = res.m_data;\n    utf8::dump(ptr, c);\n    res.m_length = (int)(ptr - res.m_data);\n    return res;\n}\n\ntemplate<typename AppendFunc>\nvoid format_impl(StringView fmt, ArrayView<const StringView> params, AppendFunc append)\n{\n    int implicitIndex = 0;\n    for (auto it = fmt.begin(), end = fmt.end(); it != end;)\n    {\n        auto opening = std::find(it, end, '{');\n        if (opening == end)\n        {\n            append(StringView{it, opening});\n            break;\n        }\n        else if (opening != it and *(opening-1) == '\\\\')\n        {\n            append(StringView{it, opening-1});\n            append('{');\n            it = opening + 1;\n        }\n        else\n        {\n            append(StringView{it, opening});\n            auto closing = std::find(opening, end, '}');\n            if (closing == end)\n                throw runtime_error(\"format string error, unclosed '{'\");\n\n            auto format = std::find(opening+1, closing, ':');\n            const int index = opening+1 == format ? implicitIndex : str_to_int({opening+1, format});\n\n            if (index >= params.size())\n                throw runtime_error(\"format string parameter index too big\");\n\n            if (format != closing)\n            {\n                char padding = ' ';\n                if (*(++format) == '0')\n                {\n                    padding = '0';\n                    ++format;\n                }\n                for (ColumnCount width = str_to_int({format, closing}), len = params[index].column_length();\n                     width > len; --width)\n                    append(padding);\n            }\n\n            append(params[index]);\n            implicitIndex = index+1;\n            it = closing+1;\n        }\n    }\n}\n\nStringView format_to(ArrayView<char> buffer, StringView fmt, ArrayView<const StringView> params)\n{\n    char* ptr = buffer.begin();\n    const char* end = buffer.end();\n    format_impl(fmt, params, [&](StringView s) mutable {\n        for (auto c : s)\n        {\n            if (ptr == end)\n                throw runtime_error(\"buffer is too small\");\n            *ptr++ = c;\n        }\n    });\n    if (ptr == end)\n        throw runtime_error(\"buffer is too small\");\n    *ptr = 0;\n\n    return { buffer.begin(), ptr };\n}\n\nvoid format_with(FunctionRef<void (StringView)> append, StringView fmt, ArrayView<const StringView> params)\n{\n    format_impl(fmt, params, append);\n}\n\nString format(StringView fmt, ArrayView<const StringView> params)\n{\n    ByteCount size = fmt.length();\n    for (auto& s : params) size += s.length();\n    String res;\n    res.reserve(size);\n\n    format_impl(fmt, params, [&](StringView s) { res += s; });\n    return res;\n}\n\n}\n"
  },
  {
    "path": "src/format.hh",
    "content": "#ifndef format_hh_INCLUDED\n#define format_hh_INCLUDED\n\n#include \"string.hh\"\n#include \"utils.hh\"\n\nnamespace Kakoune\n{\n\ntemplate<size_t N>\nstruct InplaceString\n{\n    static_assert(N < 256, \"InplaceString cannot handle sizes >= 256\");\n\n    constexpr operator StringView() const { return {m_data, ByteCount{m_length}}; }\n    operator String() const { return {m_data, ByteCount{m_length}}; }\n\n    unsigned char m_length{};\n    char m_data[N];\n};\n\nstruct Hex { size_t val; };\nconstexpr Hex hex(size_t val) { return {val}; }\n\nstruct Grouped { size_t val; };\nconstexpr Grouped grouped(size_t val) { return {val}; }\n\nInplaceString<15> to_string(int val);\nInplaceString<15> to_string(unsigned val);\nInplaceString<23> to_string(long int val);\nInplaceString<23> to_string(unsigned long val);\nInplaceString<23> to_string(long long int val);\nInplaceString<23> to_string(Hex val);\nInplaceString<23> to_string(Grouped val);\nInplaceString<23> to_string(float val);\nInplaceString<7>  to_string(Codepoint c);\n\ntemplate<typename RealType, typename ValueType>\ndecltype(auto) to_string(const StronglyTypedNumber<RealType, ValueType>& val)\n{\n    return to_string((ValueType)val);\n}\n\nnamespace detail\n{\n\ntemplate<typename T> requires std::is_convertible_v<T, StringView>\nStringView format_param(const T& val) { return val; }\n\ntemplate<typename T> requires (not std::is_convertible_v<T, StringView>)\ndecltype(auto) format_param(const T& val) { return to_string(val); }\n\n}\n\nString format(StringView fmt, ArrayView<const StringView> params);\n\ntemplate<typename... Types>\nString format(StringView fmt, Types&&... params)\n{\n    return format(fmt, ArrayView<const StringView>{detail::format_param(std::forward<Types>(params))...});\n}\n\nStringView format_to(ArrayView<char> buffer, StringView fmt, ArrayView<const StringView> params);\n\ntemplate<typename... Types>\nStringView format_to(ArrayView<char> buffer, StringView fmt, Types&&... params)\n{\n    return format_to(buffer, fmt, ArrayView<const StringView>{detail::format_param(std::forward<Types>(params))...});\n}\n\nvoid format_with(FunctionRef<void (StringView)> append, StringView fmt, ArrayView<const StringView> params);\n\ntemplate<typename... Types>\nvoid format_with(FunctionRef<void (StringView)> append, StringView fmt, Types&&... params)\n{\n    return format_with(append, fmt, ArrayView<const StringView>{detail::format_param(std::forward<Types>(params))...});\n}\n\n}\n\n#endif // format_hh_INCLUDED\n"
  },
  {
    "path": "src/function.hh",
    "content": "#ifndef function_hh_INCLUDED\n#define function_hh_INCLUDED\n\n#include \"exception.hh\"\n#include <utility>\n#include <type_traits>\n\nnamespace Kakoune\n{\n\ntemplate<typename Res, typename... Args>\nstruct FunctionVTable\n{\n    Res (*call)(void*, Args...);\n    void* (*clone)(void*);\n    void (*destroy)(void*);\n};\n\ntemplate<typename Res, typename... Args>\ninline constexpr FunctionVTable<Res, Args...> unset_vtable{\n    .call = [](void*, Args...) -> Res { throw runtime_error(\"called an empty function\"); },\n    .clone = [](void*) -> void* { return nullptr; },\n    .destroy = [](void*) {},\n};\n\ntemplate<bool Copyable, typename Target, typename Res, typename... Args>\ninline constexpr FunctionVTable<Res, Args...> vtable_for{\n    .call = [](void* target, Args... args) -> Res { return (*reinterpret_cast<Target*>(target))(std::forward<Args>(args)...); },\n    .clone = [](void* target) -> void* { if constexpr (Copyable) { return new Target(*reinterpret_cast<Target*>(target)); } return nullptr; },\n    .destroy = [](void* target) { delete reinterpret_cast<Target*>(target); }\n};\n\ntemplate<typename Res, typename... Args>\ninline constexpr FunctionVTable<Res, Args...> vtable_for_func{\n    .call = [](void* target, Args... args) -> Res { return (*reinterpret_cast<Res (*)(Args...)>(target))(std::forward<Args>(args)...); },\n    .clone = [](void* target) -> void* { return target; },\n    .destroy = [](void* target) {}\n};\n\ntemplate<bool Copyable, typename F>\nclass FunctionImpl;\n\ntemplate<bool Copyable, typename Res, typename... Args>\nclass FunctionImpl<Copyable, Res (Args...)>\n{\npublic:\n    FunctionImpl() = default;\n\n    template<typename Target>\n        requires (not std::is_same_v<FunctionImpl, std::remove_cvref_t<Target>>)\n    FunctionImpl(Target&& target)\n    {\n        using EffectiveTarget = std::remove_cvref_t<Target>;\n        if constexpr (std::is_convertible_v<EffectiveTarget, Res(*)(Args...)>)\n        {\n            m_target = reinterpret_cast<void*>(static_cast<Res(*)(Args...)>(target));\n            m_vtable = &vtable_for_func<Res, Args...>;\n        }\n        else\n        {\n            m_target = new EffectiveTarget(std::forward<Target>(target));\n            m_vtable = &vtable_for<Copyable, EffectiveTarget, Res, Args...>;\n        }\n    }\n\n    FunctionImpl(const FunctionImpl& other) requires Copyable\n        : m_target(other.m_vtable->clone(other.m_target)), m_vtable(other.m_vtable)\n    {\n    }\n\n    FunctionImpl(FunctionImpl&& other) : m_target(other.m_target), m_vtable(other.m_vtable)\n    {\n        other.m_target = nullptr;\n        other.m_vtable = &unset_vtable<Res, Args...>;\n    }\n\n    ~FunctionImpl()\n    {\n        m_vtable->destroy(m_target);\n    }\n\n    FunctionImpl& operator=(const FunctionImpl& other) requires Copyable\n    {\n        m_vtable->destroy(m_target);\n        m_target = other.m_vtable->clone(other.m_target);\n        m_vtable = other.m_vtable;\n        return *this;\n    }\n\n    FunctionImpl& operator=(FunctionImpl&& other)\n    {\n        m_vtable->destroy(m_target);\n        m_target = other.m_target;\n        m_vtable = other.m_vtable;\n        other.m_target = nullptr;\n        other.m_vtable = &unset_vtable<Res, Args...>;\n        return *this;\n    }\n\n    Res operator()(Args... args) const\n    {\n        return m_vtable->call(m_target, std::forward<Args>(args)...);\n    }\n\n    explicit operator bool() const { return m_vtable != &unset_vtable<Res, Args...>; }\n\nprivate:\n    void* m_target = nullptr;\n    const FunctionVTable<Res, Args...>* m_vtable = &unset_vtable<Res, Args...>;\n};\n\ntemplate<typename F>\nusing Function = FunctionImpl<true, F>;\n\ntemplate<typename F>\nusing MoveOnlyFunction = FunctionImpl<false, F>;\n\n}\n\n#endif // function_hh_INCLUDED\n"
  },
  {
    "path": "src/hash.cc",
    "content": "#include \"hash.hh\"\n\n#include <cstdint>\n#include <cstring>\n\n#include \"unit_tests.hh\"\n#include \"assert.hh\"\n\nnamespace Kakoune\n{\n\n[[gnu::always_inline]]\nstatic inline uint32_t rotl(uint32_t x, int8_t r)\n{\n    return (x << r) | (x >> (32 - r));\n}\n\n[[gnu::always_inline]]\nstatic inline uint32_t fmix(uint32_t h)\n{\n    h ^= h >> 16;\n    h *= 0x85ebca6b;\n    h ^= h >> 13;\n    h *= 0xc2b2ae35;\n    h ^= h >> 16;\n\n    return h;\n}\n\n// based on https://github.com/PeterScott/murmur3\nsize_t murmur3(const char* input, size_t len)\n{\n    const uint8_t* data = reinterpret_cast<const uint8_t*>(input);\n    uint32_t hash = 0x1235678;\n    constexpr uint32_t c1 = 0xcc9e2d51;\n    constexpr uint32_t c2 = 0x1b873593;\n\n    const ptrdiff_t nblocks = len / 4;\n    const uint8_t* blocks = data + nblocks*4;\n\n    for (ptrdiff_t i = -nblocks; i; ++i)\n    {\n        uint32_t key;\n        key = (blocks[4*i + 3] << 24) | (blocks[4*i + 2] << 16) | (blocks[4*i + 1] << 8) | blocks[4*i];\n        key *= c1;\n        key = rotl(key, 15);\n        key *= c2;\n\n        hash ^= key;\n        hash = rotl(hash, 13);\n        hash = hash * 5 + 0xe6546b64;\n    }\n\n    const uint8_t* tail = data + nblocks * 4;\n    uint32_t key = 0;\n    switch (len & 0b11)\n    {\n        case 3: key ^= tail[2] << 16; [[fallthrough]];\n        case 2: key ^= tail[1] << 8;  [[fallthrough]];\n        case 1: key ^= tail[0];\n                key *= c1;\n                key = rotl(key,15);\n                key *= c2;\n                hash ^= key;\n    }\n\n    hash ^= len;\n    hash = fmix(hash);\n\n    return hash;\n}\n\nUnitTest test_murmur_hash{[] {\n    {\n        constexpr char data[] = \"Hello, World!\";\n        kak_assert(murmur3(data, strlen(data)) == 0xf816f95b);\n    }\n    {\n        constexpr char data[] = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n        kak_assert(murmur3(data, strlen(data)) == 3551113186);\n    }\n    kak_assert(murmur3(\"\", 0) == 2572747774);\n}};\n\n}\n"
  },
  {
    "path": "src/hash.hh",
    "content": "#ifndef hash_hh_INCLUDED\n#define hash_hh_INCLUDED\n\n#include <type_traits>\n#include <utility>\n\n#include <cstddef>\n#include <cstdint>\n\nnamespace Kakoune\n{\n\ninline size_t fnv1a(const char* data, size_t len)\n{\n    constexpr uint32_t FNV_prime_32 = 16777619;\n    constexpr uint32_t offset_basis_32 = 2166136261;\n\n    uint32_t hash_value = offset_basis_32;\n    for (size_t i = 0; i < len; ++i)\n        hash_value = (hash_value ^ data[i]) * FNV_prime_32;\n    return hash_value;\n}\n\nsize_t murmur3(const char* input, size_t len);\n\ntemplate<typename Type> requires std::is_integral_v<Type>\nconstexpr size_t hash_value(const Type& val)\n{\n    return (size_t)val;\n}\n\ntemplate<typename Type> requires std::is_enum_v<Type>\nconstexpr size_t hash_value(const Type& val)\n{\n    return hash_value((std::underlying_type_t<Type>)val);\n}\n\ntemplate<typename Type>\nconstexpr size_t hash_values(Type&& t)\n{\n    return hash_value(std::forward<Type>(t));\n}\n\nconstexpr size_t combine_hash(size_t lhs, size_t rhs)\n{\n    return lhs ^ (rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2));\n}\n\ntemplate<typename Type, typename... RemainingTypes>\nconstexpr size_t hash_values(Type&& t, RemainingTypes&&... rt)\n{\n    size_t seed = hash_values(std::forward<RemainingTypes>(rt)...);\n    return combine_hash(seed, hash_value(std::forward<Type>(t)));\n}\n\ntemplate<typename T1, typename T2>\nconstexpr size_t hash_value(const std::pair<T1, T2>& val)\n{\n    return hash_values(val.first, val.second);\n}\n\ntemplate<typename Type>\nstruct Hash\n{\n    constexpr size_t operator()(const Type& val) const\n    {\n        return hash_value(val);\n    }\n};\n\n// Traits specifying if two types have compatible hashing, that is,\n// if lhs == rhs => hash_value(lhs) == hash_value(rhs)\ntemplate<typename Lhs, typename Rhs>\nstruct HashCompatible : std::false_type {};\n\ntemplate<typename T> struct HashCompatible<T, T> : std::true_type {};\n\ntemplate<typename Lhs, typename Rhs>\nconstexpr bool IsHashCompatible = HashCompatible<Lhs, Rhs>::value;\n\n}\n\n#endif // hash_hh_INCLUDED\n"
  },
  {
    "path": "src/hash_map.cc",
    "content": "#include \"hash_map.hh\"\n\n#include \"clock.hh\"\n#include \"string.hh\"\n#include \"unit_tests.hh\"\n#include \"format.hh\"\n#include \"debug.hh\"\n\n#include <random>\n#include <algorithm>\n#include <unordered_map>\n\nnamespace Kakoune\n{\n\nUnitTest test_hash_map{[] {\n    // Basic usage\n    {\n        HashMap<int, int> map;\n        map.insert({10, 1});\n        map.insert({20, 2});\n        kak_assert(map.find_index(0) == -1);\n        kak_assert(map.find_index(10) == 0);\n        kak_assert(map.find_index(20) == 1);\n        kak_assert(map[10] == 1);\n        kak_assert(map[20] == 2);\n        kak_assert(map[30] == 0);\n        map[30] = 3;\n        kak_assert(map.find_index(30) == 2);\n        map.remove(20);\n        kak_assert(map.find_index(30) == 1);\n        kak_assert(map.size() == 2);\n    }\n\n    // Replace Multiple entries with the same key\n    {\n        HashMap<int, int> map;\n        map.insert({10, 1});\n        map.insert({10, 2});\n        kak_assert(map.find_index(10) == 0);\n        kak_assert(map.size() == 1);\n        kak_assert(map[10] == 2);\n        map.remove(10);\n        kak_assert(map.find_index(10) == -1);\n    }\n\n    // Multiple entries with the same key\n    {\n        MultiHashMap<int, int> map;\n        map.insert({10, 1});\n        map.insert({10, 2});\n        kak_assert(map.find_index(10) == 0);\n        map.remove(10);\n        kak_assert(map.find_index(10) == 0);\n        map.remove(10);\n        kak_assert(map.find_index(10) == -1);\n        map.insert({20, 1});\n        map.insert({20, 2});\n        map.remove_all(20);\n        kak_assert(map.find_index(20) == -1);\n    }\n\n    // Check hash compatible support\n    {\n        HashMap<String, int> map;\n        map.insert({\"test\", 10});\n        kak_assert(map[\"test\"_sv] == 10);\n        map.remove(\"test\"_sv);\n    }\n\n    // make sure we get what we expect from the hash map\n    {\n        std::random_device dev;\n        std::default_random_engine re{dev()};\n        std::uniform_int_distribution<int> dist;\n\n        HashMap<int, int> map;\n        Vector<std::pair<int, int>> ref;\n\n        for (int i = 0; i < 100; ++i)\n        {\n            auto key = dist(re), value = dist(re);\n            ref.push_back({key, value});\n            map.insert({key, value});\n\n            std::shuffle(ref.begin(), ref.end(), re);\n            for (auto& elem : ref)\n            {\n                auto it = map.find(elem.first);\n                kak_assert(it != map.end() and it->value == elem.second);\n            }\n        }\n    }\n}};\n\nUnitTest test_hash_set{[] {\n    // Basic usage\n    {\n        HashSet<int> set;\n        set.insert(10);\n        set.insert(20);\n        kak_assert(set.find_index(0) == -1);\n        kak_assert(set.find_index(10) == 0);\n        kak_assert(set.find_index(20) == 1);\n        kak_assert(set[10] == 10);\n        kak_assert(set[20] == 20);\n        kak_assert(set[30] == 30);\n        set[30];\n        kak_assert(set.find_index(30) == 2);\n        set.remove(20);\n        kak_assert(set.find_index(30) == 1);\n        kak_assert(set.size() == 2);\n    }\n\n    // Replace Multiple entries with the same key\n    {\n        HashSet<int> set;\n        set.insert(10);\n        set.insert(10);\n        kak_assert(set.find_index(10) == 0);\n        kak_assert(set.size() == 1);\n        set.remove(10);\n        kak_assert(set.find_index(10) == -1);\n    }\n\n    // Multiple entries with the same key\n    {\n        MultiHashSet<int> set;\n        set.insert(10);\n        set.insert(10);\n        kak_assert(set.find_index(10) == 0);\n        set.remove(10);\n        kak_assert(set.find_index(10) == 0);\n        set.remove(10);\n        kak_assert(set.find_index(10) == -1);\n        set.insert(20);\n        set.insert(20);\n        set.remove_all(20);\n        kak_assert(set.find_index(20) == -1);\n    }\n\n    // Check hash compatible support\n    {\n        HashSet<String> set;\n        set.insert(\"test\");\n        kak_assert(set[\"test\"_sv] == \"test\");\n        set.remove(\"test\"_sv);\n    }\n}};\n\ntemplate<typename Map>\nvoid do_profile(size_t count, StringView type)\n{\n    std::random_device dev;\n    std::default_random_engine re{dev()};\n    std::uniform_int_distribution<size_t> dist{0, count};\n\n    Vector<size_t> vec;\n    for (size_t i = 0; i < count; ++i)\n        vec.push_back(i);\n    std::shuffle(vec.begin(), vec.end(), re);\n\n    Map map;\n    auto start = Clock::now();\n\n    for (auto v : vec)\n        map.insert({v, dist(re)});\n    auto after_insert = Clock::now();\n\n    for (size_t i = 0; i < count; ++i)\n        ++map[dist(re)];\n    auto after_read = Clock::now();\n\n    for (size_t i = 0; i < count; ++i)\n        map.erase(dist(re));\n    auto after_remove = Clock::now();\n\n    int c = 0;\n    for (auto v : vec)\n    {\n        auto it = map.find(v);\n        if (it != map.end())\n            ++c;\n    }\n    auto after_find = Clock::now();\n\n    using namespace std::chrono;\n    write_to_debug_buffer(format(\"{} ({}) -- inserts: {}us, reads: {}us, remove: {}us, find: {}us ({})\", type, count,\n                                 duration_cast<microseconds>(after_insert - start).count(),\n                                 duration_cast<microseconds>(after_read - after_insert).count(),\n                                 duration_cast<microseconds>(after_remove - after_read).count(),\n                                 duration_cast<microseconds>(after_find - after_remove).count(),\n                                 c));\n}\n\nvoid profile_hash_maps()\n{\n    for (auto i : { 1000, 10000, 100000, 1000000, 10000000 })\n    {\n        do_profile<std::unordered_map<size_t, size_t>>(i, \"UnorderedMap\");\n        do_profile<HashMap<size_t, size_t>>(i, \"     HashMap\");\n    }\n}\n\n}\n"
  },
  {
    "path": "src/hash_map.hh",
    "content": "#ifndef hash_map_hh_INCLUDED\n#define hash_map_hh_INCLUDED\n\n#include \"hash.hh\"\n#include \"memory.hh\"\n#include \"vector.hh\"\n\nnamespace Kakoune\n{\n\ntemplate<typename T>\nconstexpr void constexpr_swap(T& lhs, T& rhs)\n{\n    T tmp = std::move(lhs);\n    lhs = std::move(rhs);\n    rhs = std::move(tmp);\n}\n\ntemplate<MemoryDomain domain,\n         template<typename, MemoryDomain> class Container>\nstruct HashIndex\n{\n    struct Entry\n    {\n        size_t hash = 0;\n        int index = -1;\n    };\n\n    static constexpr float max_fill_rate = 0.5f;\n\n    constexpr HashIndex() = default;\n    constexpr HashIndex(size_t count)\n    {\n        const size_t min_size = (size_t)(count / max_fill_rate) + 1;\n        size_t new_size = 4;\n        while (new_size < min_size)\n            new_size *= 2;\n        m_entries.resize(new_size);\n    }\n\n    using ContainerType = Container<Entry, domain>;\n\n    constexpr void resize(size_t new_size)\n    {\n        kak_assert(new_size > m_entries.size());\n        ContainerType old_entries = std::move(m_entries);\n        m_entries.resize(new_size);\n        for (auto& entry : old_entries)\n        {\n            if (entry.index >= 0)\n                add(entry.hash, entry.index);\n        }\n    }\n\n    constexpr void reserve(size_t count)\n    {\n        if (count == 0)\n            return;\n\n        const size_t min_size = (size_t)(count / max_fill_rate) + 1;\n        size_t new_size = m_entries.empty() ? 4 : m_entries.size();\n        while (new_size < min_size)\n            new_size *= 2;\n\n        if (new_size > m_entries.size())\n            resize(new_size);\n    }\n\n    constexpr void add(size_t hash, int index)\n    {\n        Entry entry{hash, index};\n        while (true)\n        {\n            auto target_slot = compute_slot(entry.hash);\n            for (auto slot = target_slot; slot < m_entries.size(); ++slot)\n            {\n                if (m_entries[slot].index < 0)\n                {\n                    m_entries[slot] = entry;\n                    return;\n                }\n\n                // Robin hood hashing\n                auto candidate_slot = compute_slot(m_entries[slot].hash);\n                if (target_slot < candidate_slot)\n                {\n                    constexpr_swap(m_entries[slot], entry);\n                    target_slot = candidate_slot;\n                }\n            }\n            // no free entries found, resize, try again\n            resize(m_entries.size() * 2);\n        }\n    }\n\n    constexpr void remove(size_t hash, int index)\n    {\n        for (auto slot = compute_slot(hash); slot < m_entries.size(); ++slot)\n        {\n            kak_assert(m_entries[slot].index >= 0);\n            if (m_entries[slot].index == index)\n            {\n                m_entries[slot].index = -1;\n                // Recompact following entries\n                for (auto next = slot+1; next < m_entries.size(); ++next)\n                {\n                    if (m_entries[next].index < 0 or\n                        compute_slot(m_entries[next].hash) == next)\n                        break;\n                    kak_assert(compute_slot(m_entries[next].hash) < next);\n                    constexpr_swap(m_entries[next-1], m_entries[next]);\n                }\n                break;\n            }\n        }\n    }\n\n    constexpr void ordered_fix_entries(int index)\n    {\n        for (auto& entry : m_entries)\n        {\n            if (entry.index >= index)\n                --entry.index;\n        }\n    }\n\n    constexpr void unordered_fix_entries(size_t hash, int old_index, int new_index)\n    {\n        for (auto slot = compute_slot(hash); slot < m_entries.size(); ++slot)\n        {\n            if (m_entries[slot].index == old_index)\n            {\n                m_entries[slot].index = new_index;\n                return;\n            }\n        }\n        kak_assert(false); // entry not found ?!\n    }\n\n    constexpr const Entry& operator[](size_t index) const { return m_entries[index]; }\n    constexpr size_t size() const { return m_entries.size(); }\n    constexpr size_t compute_slot(size_t hash) const\n    {\n        // We assume entries.size() is power of 2\n        return hash & (m_entries.size()-1);\n    }\n\n    constexpr void clear() { m_entries.clear(); }\n\nprivate:\n    ContainerType m_entries;\n};\n\ntemplate<typename Key, typename Value>\nstruct HashItem\n{\n    Key key{};\n    Value value{};\n\n    friend bool operator==(const HashItem&, const HashItem&) = default;\n};\n\ntemplate<typename Key>\nstruct HashItem<Key, void>\n{\n    Key key;\n\n    friend bool operator==(const HashItem&, const HashItem&) = default;\n};\n\ntemplate<typename Key, typename Value,\n         MemoryDomain domain = MemoryDomain::Undefined,\n         template<typename, MemoryDomain> class Container = Vector,\n         bool multi_key = false>\nstruct HashMap\n{\n    static constexpr bool has_value = not std::is_void_v<Value>;\n    using Item = std::conditional_t<has_value, HashItem<Key, Value>, Key>;\n    using EffectiveValue = std::conditional_t<has_value, Value, const Key>;\n    using ContainerType = Container<Item, domain>;\n\n    constexpr HashMap() = default;\n\n    constexpr HashMap(std::initializer_list<Item> val) : m_items(val), m_index(val.size())\n    {\n        for (int i = 0; i < m_items.size(); ++i)\n            m_index.add(hash_value(m_items[i].key), i);\n    }\n\n    template<typename Iterator>\n    constexpr HashMap(Iterator begin, Iterator end)\n    {\n        while (begin != end)\n            insert(*begin++);\n    }\n\n    constexpr EffectiveValue& insert(Item item, size_t hash)\n    {\n        kak_assert(hash == hash_value(item_key(item)));\n        if constexpr (not multi_key)\n        {\n            if (auto index = find_index(item_key(item), hash); index >= 0)\n            {\n                m_items[index] = std::move(item);\n                return item_value(m_items[index]);\n            }\n        }\n\n        m_index.reserve(m_items.size()+1);\n        m_index.add(hash, (int)m_items.size());\n        m_items.push_back(std::move(item));\n        return item_value(m_items.back());\n    }\n\n    constexpr EffectiveValue& insert(Item item)\n    {\n        const auto hash = hash_value(item_key(item));\n        return insert(std::move(item), hash);\n    }\n\n    template<typename KeyType> requires IsHashCompatible<Key, KeyType>\n    constexpr int find_index(const KeyType& key, size_t hash) const\n    {\n        for (auto slot = m_index.compute_slot(hash); slot < m_index.size(); ++slot)\n        {\n            auto& entry = m_index[slot];\n            if (entry.index < 0)\n                return -1;\n            if (entry.hash == hash and item_key(m_items[entry.index]) == key)\n                return entry.index;\n        }\n        return -1;\n    }\n\n    template<typename KeyType> requires IsHashCompatible<Key, KeyType>\n    constexpr int find_index(const KeyType& key) const { return find_index(key, hash_value(key)); }\n\n    template<typename KeyType> requires IsHashCompatible<Key, KeyType>\n    constexpr bool contains(const KeyType& key) const { return find_index(key) >= 0; }\n\n    template<typename KeyType> requires IsHashCompatible<Key, std::remove_cvref_t<KeyType>>\n    constexpr EffectiveValue& operator[](KeyType&& key)\n    {\n        const auto hash = hash_value(key);\n        auto index = find_index(key, hash);\n        if (index >= 0)\n            return item_value(m_items[index]);\n\n        m_index.reserve(m_items.size()+1);\n        m_index.add(hash, (int)m_items.size());\n        m_items.push_back({Key(std::forward<KeyType>(key))});\n        return item_value(m_items.back());\n    }\n\n    template<typename KeyType> requires IsHashCompatible<Key, std::remove_cvref_t<KeyType>>\n    constexpr const EffectiveValue& get(KeyType&& key) const\n    {\n        return const_cast<HashMap&>(*this).get(key);\n    }\n\n    template<typename KeyType> requires IsHashCompatible<Key, std::remove_cvref_t<KeyType>>\n    constexpr EffectiveValue& get(KeyType&& key)\n    {\n        const auto hash = hash_value(key);\n        auto index = find_index(key, hash);\n        kak_assert(index >= 0);\n        return item_value(m_items[index]);\n    }\n\n    template<typename KeyType> requires IsHashCompatible<Key, KeyType>\n    constexpr void remove(const KeyType& key)\n    {\n        const auto hash = hash_value(key);\n        int index = find_index(key, hash);\n        if (index >= 0)\n        {\n            [[maybe_unused]] Item keepalive = std::move(m_items[index]);\n            m_items.erase(m_items.begin() + index);\n            m_index.remove(hash, index);\n            m_index.ordered_fix_entries(index);\n        }\n    }\n\n    template<typename KeyType> requires IsHashCompatible<Key, KeyType>\n    constexpr void unordered_remove(const KeyType& key)\n    {\n        const auto hash = hash_value(key);\n        int index = find_index(key, hash);\n        if (index >= 0)\n        {\n            [[maybe_unused]] Item keepalive = std::move(m_items[index]);\n            m_items[index] = std::move(m_items.back());\n            m_items.pop_back();\n            m_index.remove(hash, index);\n            if (index != m_items.size())\n                m_index.unordered_fix_entries(hash_value(item_key(m_items[index])), m_items.size(), index);\n        }\n    }\n\n    template<typename KeyType> requires IsHashCompatible<Key, KeyType>\n    constexpr void erase(const KeyType& key) { return unordered_remove(key); }\n\n    template<typename KeyType> requires IsHashCompatible<Key, KeyType>\n    constexpr void remove_all(const KeyType& key)\n    {\n        const auto hash = hash_value(key);\n        for (int index = find_index(key, hash); index >= 0;\n             index = find_index(key, hash))\n        {\n            [[maybe_unused]] Item keepalive = std::move(m_items[index]);\n            m_items.erase(m_items.begin() + index);\n            m_index.remove(hash, index);\n            m_index.ordered_fix_entries(index);\n        }\n    }\n\n    using iterator = typename ContainerType::iterator;\n    constexpr iterator begin() { return m_items.begin(); }\n    constexpr iterator end() { return m_items.end(); }\n\n    using const_iterator = typename ContainerType::const_iterator;\n    constexpr const_iterator begin() const { return m_items.begin(); }\n    constexpr const_iterator end() const { return m_items.end(); }\n\n    Item& item(size_t index) { return m_items[index]; }\n    const Item& item(size_t index) const { return m_items[index]; }\n\n    template<typename KeyType> requires IsHashCompatible<Key, KeyType>\n    constexpr iterator find(const KeyType& key)\n    {\n        auto index = find_index(key);\n        return index >= 0 ? begin() + index : end();\n    }\n\n    template<typename KeyType> requires IsHashCompatible<Key, KeyType>\n    constexpr const_iterator find(const KeyType& key) const\n    {\n        return const_cast<HashMap*>(this)->find(key);\n    }\n\n    constexpr void remove(const const_iterator& it)\n    {\n        auto index = it - m_items.begin();\n        const auto hash = hash_value(it->key);\n        m_index.remove(hash, index);\n        m_items.erase(it);\n        m_index.ordered_fix_entries(index);\n    }\n\n    constexpr void clear() { m_items.clear(); m_index.clear(); }\n\n    constexpr size_t size() const { return m_items.size(); }\n    constexpr bool empty() const { return m_items.empty(); }\n    constexpr void reserve(size_t size)\n    {\n        m_items.reserve(size);\n        m_index.reserve(size);\n    }\n\n    // Equality is taking the order of insertion into account\n    template<MemoryDomain otherDomain>\n    constexpr bool operator==(const HashMap<Key, Value, otherDomain, Container>& other) const\n    {\n        return size() == other.size() and std::equal(begin(), end(), other.begin());\n    }\n\nprivate:\n    static auto& item_value(auto& item)\n    {\n        if constexpr (has_value) { return item.value; } else { return item; }\n    }\n\n    static const Key& item_key(const Item& item)\n    {\n        if constexpr (has_value) { return item.key; } else { return item; }\n    }\n\n    ContainerType m_items;\n    HashIndex<domain, Container> m_index;\n};\n\ntemplate<typename Key, typename Value,\n         MemoryDomain domain = MemoryDomain::Undefined,\n         template<typename, MemoryDomain> class Container = Vector>\n using MultiHashMap = HashMap<Key, Value, domain, Container, true>;\n\ntemplate<typename Value,\n         MemoryDomain domain = MemoryDomain::Undefined,\n         template<typename, MemoryDomain> class Container = Vector>\n using HashSet = HashMap<Value, void, domain, Container>;\n\ntemplate<typename Value,\n         MemoryDomain domain = MemoryDomain::Undefined,\n         template<typename, MemoryDomain> class Container = Vector>\n using MultiHashSet = HashMap<Value, void, domain, Container, true>;\n\nvoid profile_hash_maps();\n\n}\n\n#endif // hash_map_hh_INCLUDED\n"
  },
  {
    "path": "src/highlighter.cc",
    "content": "#include \"highlighter.hh\"\n\n#include \"debug.hh\"\n#include \"flags.hh\"\n\nnamespace Kakoune\n{\n\nvoid Highlighter::highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range)\n{\n    if (context.pass & m_passes) try\n    {\n        do_highlight(context, display_buffer, range);\n    }\n    catch (runtime_error& error)\n    {\n        write_to_debug_buffer(format(\"Error while highlighting: {}\", error.what()));\n    }\n}\n\nvoid Highlighter::compute_display_setup(HighlightContext context, DisplaySetup& setup) const\n{\n    if (context.pass & m_passes)\n        do_compute_display_setup(context, setup);\n}\n\n\nbool Highlighter::has_children() const\n{\n    return false;\n}\n\nHighlighter& Highlighter::get_child(StringView path)\n{\n    throw runtime_error(\"this highlighter does not hold children\");\n}\n\nvoid Highlighter::add_child(String, UniquePtr<Highlighter>&&, bool)\n{\n    throw runtime_error(\"this highlighter does not hold children\");\n}\n\nvoid Highlighter::remove_child(StringView id)\n{\n    throw runtime_error(\"this highlighter does not hold children\");\n}\n\nCompletions Highlighter::complete_child(StringView path, ByteCount cursor_pos, bool group) const\n{\n    throw runtime_error(\"this highlighter does not hold children\");\n}\n\nvoid Highlighter::fill_unique_ids(Vector<StringView>& unique_ids) const\n{}\n\n}\n"
  },
  {
    "path": "src/highlighter.hh",
    "content": "#ifndef highlighter_hh_INCLUDED\n#define highlighter_hh_INCLUDED\n\n#include \"coord.hh\"\n#include \"completion.hh\"\n#include \"range.hh\"\n#include \"hash_map.hh\"\n#include \"array_view.hh\"\n#include \"string.hh\"\n#include \"utils.hh\"\n#include \"parameters_parser.hh\"\n#include \"unique_ptr.hh\"\n\nnamespace Kakoune\n{\n\nclass Context;\nclass DisplayBuffer;\n\nusing BufferRange = Range<BufferCoord>;\n\nenum class HighlightPass\n{\n    Replace = 1 << 0,\n    Wrap = 1 << 1,\n    Move = 1 << 2,\n    Colorize = 1 << 3,\n\n    All = Replace | Wrap | Move | Colorize,\n};\nconstexpr bool with_bit_ops(Meta::Type<HighlightPass>) { return true; }\n\n// A Highlighter is a function which mutates a DisplayBuffer in order to\n// change the visual representation of a file. It could be changing text\n// color, adding information text (line numbering for example) or replacing\n// buffer content (folding for example)\n\nstruct Highlighter;\n\nstruct DisplaySetup\n{\n    LineCount first_line;\n    LineCount line_count;\n    ColumnCount first_column;\n    ColumnCount widget_columns;\n    // Offset of line and columns that must remain visible around cursor\n    DisplayCoord scroll_offset;\n};\n\nusing HighlighterIdList = ConstArrayView<StringView>;\n\nstruct HighlightContext\n{\n    const Context& context;\n    const DisplaySetup& setup;\n    HighlightPass pass;\n    HighlighterIdList disabled_ids;\n};\n\nstruct Highlighter\n{\n    Highlighter(HighlightPass passes) : m_passes{passes} {}\n    virtual ~Highlighter() = default;\n\n    void highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range);\n    void compute_display_setup(HighlightContext context, DisplaySetup& setup) const;\n\n    virtual bool has_children() const;\n    virtual Highlighter& get_child(StringView path);\n    virtual void add_child(String name, UniquePtr<Highlighter>&& hl, bool override = false);\n    virtual void remove_child(StringView id);\n    virtual Completions complete_child(StringView path, ByteCount cursor_pos, bool group) const;\n    virtual void fill_unique_ids(Vector<StringView>& unique_ids) const;\n\n    HighlightPass passes() const { return m_passes; }\n\nprivate:\n    virtual void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) = 0;\n    virtual void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const {}\n\n    const HighlightPass m_passes;\n};\n\nusing HighlighterParameters = ConstArrayView<String>;\nusing HighlighterFactory = UniquePtr<Highlighter> (*)(HighlighterParameters params, Highlighter* parent);\n\nstruct HighlighterDesc\n{\n    const char* docstring;\n    ParameterDesc params;\n};\n\nstruct HighlighterFactoryAndDescription\n{\n    HighlighterFactory factory;\n    const HighlighterDesc* description;\n};\n\nstruct HighlighterRegistry : HashMap<String, HighlighterFactoryAndDescription, MemoryDomain::Highlight>,\n                             Singleton<HighlighterRegistry>\n{};\n\n}\n\n#endif // highlighter_hh_INCLUDED\n"
  },
  {
    "path": "src/highlighter_group.cc",
    "content": "#include \"highlighter_group.hh\"\n\n#include \"flags.hh\"\n#include \"format.hh\"\n#include \"ranges.hh\"\n\n\nnamespace Kakoune\n{\n\nvoid HighlighterGroup::do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range)\n{\n    for (auto& hl : m_highlighters)\n        hl.value->highlight(context, display_buffer, range);\n}\n\nvoid HighlighterGroup::do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const\n{\n    for (auto& hl : m_highlighters)\n        hl.value->compute_display_setup(context, setup);\n}\n\nvoid HighlighterGroup::fill_unique_ids(Vector<StringView>& unique_ids) const\n{\n    for (auto& hl : m_highlighters)\n        hl.value->fill_unique_ids(unique_ids);\n}\n\nvoid HighlighterGroup::add_child(String name, UniquePtr<Highlighter>&& hl, bool override)\n{\n    if ((hl->passes() & passes()) != hl->passes())\n        throw runtime_error{\"cannot add that highlighter to this group, passes don't match\"};\n\n    auto it = m_highlighters.find(name);\n    if (it != m_highlighters.end())\n    {\n        if (not override)\n            throw runtime_error(format(\"duplicate id: '{}'\", name));\n        it->value = std::move(hl);\n        return;\n    }\n\n    m_highlighters.insert({std::move(name), std::move(hl)});\n}\n\nvoid HighlighterGroup::remove_child(StringView id)\n{\n    auto it = m_highlighters.find(id);\n    if (it == m_highlighters.end())\n        throw child_not_found(format(\"no such id: '{}'\", id));\n    m_highlighters.remove(it);\n}\n\nHighlighter& HighlighterGroup::get_child(StringView path)\n{\n    auto sep_it = find(path, '/');\n    StringView id(path.begin(), sep_it);\n    auto it = m_highlighters.find(id);\n    if (it == m_highlighters.end())\n        throw child_not_found(format(\"no such id: '{}'\", id));\n    if (sep_it == path.end())\n        return *it->value;\n    else\n        return it->value->get_child({sep_it+1, path.end()});\n}\n\nCompletions HighlighterGroup::complete_child(StringView path, ByteCount cursor_pos, bool group) const\n{\n    auto sep_it = find(path, '/');\n    if (sep_it != path.end())\n    {\n        ByteCount offset = sep_it+1 - path.begin();\n        Highlighter& hl = const_cast<HighlighterGroup*>(this)->get_child({path.begin(), sep_it});\n        return offset_pos(hl.complete_child(path.substr(offset), cursor_pos - offset, group), offset);\n    }\n\n    auto candidates = complete(\n        path, cursor_pos,\n        m_highlighters | filter([=](auto& hl) { return not group or hl.value->has_children(); })\n                       | transform([](auto& hl) { return hl.value->has_children() ? hl.key + \"/\" : hl.key; })\n                       | gather<Vector<String>>());\n\n    auto completions_flags = group ? Completions::Flags::None : Completions::Flags::Menu;\n    return { 0, 0, std::move(candidates), completions_flags };\n}\n\nvoid Highlighters::highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range)\n{\n    Vector<StringView> disabled_ids(context.disabled_ids.begin(), context.disabled_ids.end());\n    m_group.fill_unique_ids(disabled_ids);\n\n    if (m_parent)\n        m_parent->highlight({context.context, context.setup, context.pass, disabled_ids}, display_buffer, range);\n    m_group.highlight(context, display_buffer, range);\n}\n\nvoid Highlighters::compute_display_setup(HighlightContext context, DisplaySetup& setup) const\n{\n    Vector<StringView> disabled_ids(context.disabled_ids.begin(), context.disabled_ids.end());\n    m_group.fill_unique_ids(disabled_ids);\n\n    if (m_parent)\n        m_parent->compute_display_setup({context.context, context.setup, context.pass, disabled_ids}, setup);\n    m_group.compute_display_setup(context, setup);\n}\n\n}\n"
  },
  {
    "path": "src/highlighter_group.hh",
    "content": "#ifndef highlighter_group_hh_INCLUDED\n#define highlighter_group_hh_INCLUDED\n\n#include \"exception.hh\"\n#include \"hash_map.hh\"\n#include \"highlighter.hh\"\n#include \"utils.hh\"\n#include \"safe_ptr.hh\"\n\nnamespace Kakoune\n{\n\nstruct child_not_found : public runtime_error\n{\n    using runtime_error::runtime_error;\n};\n\nclass HighlighterGroup : public Highlighter\n{\npublic:\n    HighlighterGroup(HighlightPass passes) : Highlighter{passes} {}\n\n    bool has_children() const override { return true; }\n    void add_child(String name, UniquePtr<Highlighter>&& hl, bool override = false) override;\n    void remove_child(StringView id) override;\n\n    Highlighter& get_child(StringView path) override;\n\n    Completions complete_child(StringView path, ByteCount cursor_pos, bool group) const override;\n\n    void fill_unique_ids(Vector<StringView>& unique_ids) const override;\n\nprotected:\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override;\n    void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const override;\n\n    using HighlighterMap = HashMap<String, UniquePtr<Highlighter>, MemoryDomain::Highlight>;\n    HighlighterMap m_highlighters;\n};\n\nclass Highlighters : public SafeCountable\n{\npublic:\n    Highlighters(Highlighters& parent) : SafeCountable{}, m_parent{&parent}, m_group{HighlightPass::All} {}\n\n    void reparent(Highlighters& parent) { m_parent = &parent; }\n\n    HighlighterGroup& group() { return m_group; }\n    const HighlighterGroup& group() const { return m_group; }\n\n    void highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range);\n    void compute_display_setup(HighlightContext context, DisplaySetup& setup) const;\n\nprivate:\n    friend class Scope;\n    Highlighters() : m_group{HighlightPass::All} {}\n\n    SafePtr<Highlighters> m_parent;\n    HighlighterGroup m_group;\n};\n\nstruct SharedHighlighters : public HighlighterGroup,\n                            public Singleton<SharedHighlighters>\n{\n    SharedHighlighters() : HighlighterGroup{HighlightPass::All} {}\n};\n\n}\n\n#endif // highlighter_group_hh_INCLUDED\n"
  },
  {
    "path": "src/highlighters.cc",
    "content": "#include \"highlighters.hh\"\n\n#include \"assert.hh\"\n#include \"buffer_utils.hh\"\n#include \"debug.hh\"\n#include \"changes.hh\"\n#include \"command_manager.hh\"\n#include \"context.hh\"\n#include \"display_buffer.hh\"\n#include \"face_registry.hh\"\n#include \"highlighter_group.hh\"\n#include \"line_modification.hh\"\n#include \"option_manager.hh\"\n#include \"option_types.hh\"\n#include \"parameters_parser.hh\"\n#include \"ranges.hh\"\n#include \"regex.hh\"\n#include \"register_manager.hh\"\n#include \"string.hh\"\n#include \"utf8.hh\"\n#include \"utf8_iterator.hh\"\n#include \"window.hh\"\n#include \"unicode.hh\"\n\n#include <cstdio>\n#include <limits>\n\nnamespace Kakoune\n{\n\ntemplate<typename Func>\nUniquePtr<Highlighter> make_highlighter(Func func, HighlightPass pass = HighlightPass::Colorize)\n{\n    struct SimpleHighlighter : public Highlighter\n    {\n        SimpleHighlighter(Func func, HighlightPass pass)\n          : Highlighter{pass}, m_func{std::move(func)} {}\n\n    private:\n        void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override\n        {\n            m_func(context, display_buffer, range);\n        }\n        Func m_func;\n    };\n    return make_unique_ptr<SimpleHighlighter>(std::move(func), pass);\n}\n\ntemplate<typename T>\nvoid highlight_range(DisplayBuffer& display_buffer,\n                     BufferCoord begin, BufferCoord end,\n                     bool skip_replaced, T func)\n{\n    // tolerate begin > end as that can be triggered by wrong encodings\n    if (begin >= end or end <= display_buffer.range().begin\n                     or begin >= display_buffer.range().end)\n        return;\n\n    for (auto& line : display_buffer.lines())\n    {\n        auto& range = line.range();\n        if (range.end <= begin or end < range.begin)\n            continue;\n\n        for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it)\n        {\n            bool is_replaced = atom_it->type() == DisplayAtom::ReplacedRange;\n\n            if (not atom_it->has_buffer_range() or\n                (skip_replaced and is_replaced) or\n                end <= atom_it->begin() or begin >= atom_it->end())\n                continue;\n\n            if (not is_replaced and begin > atom_it->begin())\n                atom_it = ++line.split(atom_it, begin);\n\n            if (not is_replaced and end < atom_it->end())\n            {\n                atom_it = line.split(atom_it, end);\n                func(*atom_it);\n                ++atom_it;\n            }\n            else\n                func(*atom_it);\n        }\n    }\n}\n\ntemplate<typename T>\nvoid replace_range(DisplayBuffer& display_buffer,\n                   BufferCoord begin, BufferCoord end, T func)\n{\n    // tolerate begin > end as that can be triggered by wrong encodings\n    if (begin > end or end < display_buffer.range().begin or begin > display_buffer.range().end)\n        return;\n\n    auto& lines = display_buffer.lines();\n    auto first_it = std::lower_bound(lines.begin(), lines.end(), begin, [](const DisplayLine& l, const BufferCoord& c) { return l.range().end < c; });\n    if (first_it == lines.end())\n        return;\n\n    auto first_atom_it = std::find_if(first_it->begin(), first_it->end(), [&begin](const DisplayAtom& a) { return a.has_buffer_range() and a.end() > begin; });\n    first_atom_it = first_it->split(begin);\n\n    auto last_it = std::lower_bound(first_it, lines.end(), end, [](const DisplayLine& l, const BufferCoord& c) { return l.range().end < c; });\n\n    if (first_it == last_it)\n    {\n        auto first_atom_idx = first_atom_it - first_it->begin();\n        auto end_atom_it = first_it->split(end);\n        first_atom_it = first_it->erase(first_it->begin() + first_atom_idx, end_atom_it);\n    }\n    else\n    {\n        first_atom_it = first_it->erase(first_atom_it, first_it->end());\n        if (last_it != lines.end())\n        {\n            auto end_atom_it = last_it->split(end);\n            end_atom_it = last_it->erase(last_it->begin(), end_atom_it);\n\n            first_atom_it = first_it->insert(first_atom_it, end_atom_it, last_it->end());\n            ++last_it;\n        }\n        first_it = --lines.erase(first_it+1, last_it);\n    }\n\n    func(lines, first_it, first_atom_it);\n}\n\nauto apply_face = [](const Face& face)\n{\n    return [&face](DisplayAtom& atom) {\n        atom.face = merge_faces(atom.face, face);\n    };\n};\n\nconst HighlighterDesc fill_desc = {\n    \"Fill the whole highlighted range with the given face\",\n    {}\n};\nstatic UniquePtr<Highlighter> create_fill_highlighter(HighlighterParameters params, Highlighter*)\n{\n    if (params.size() != 1)\n        throw runtime_error(\"wrong parameter count\");\n\n    return make_highlighter(\n        [spec=parse_face(params[0])](HighlightContext context, DisplayBuffer& display_buffer,\n                                     BufferRange range) {\n            highlight_range(display_buffer, range.begin, range.end, false,\n                            apply_face(context.context.faces()[spec]));\n        });\n}\n\ntemplate<typename T>\nstruct BufferSideCache\n{\n    BufferSideCache() : m_id{get_free_value_id()} {}\n\n    T& get(const Buffer& buffer)\n    {\n        Value& cache_val = buffer.values()[m_id];\n        if (not cache_val)\n            cache_val = Value(T{});\n        return cache_val.as<T>();\n    }\nprivate:\n    ValueId m_id;\n};\n\nusing FacesSpec = Vector<std::pair<size_t, FaceSpec>, MemoryDomain::Highlight>;\n\nconst HighlighterDesc regex_desc = {\n    \"Parameters: <regex> <capture num>:<face> <capture num>:<face>...\\n\"\n    \"Highlights the matches for captures from the regex with the given faces\",\n    {}\n};\nclass RegexHighlighter : public Highlighter\n{\npublic:\n    RegexHighlighter(Regex regex, FacesSpec faces)\n        : Highlighter{HighlightPass::Colorize},\n          m_regex{std::move(regex)},\n          m_faces{std::move(faces)}\n    {\n        ensure_first_face_is_capture_0();\n    }\n\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override\n    {\n        auto overlaps = [](const BufferRange& lhs, const BufferRange& rhs) {\n            return lhs.begin < rhs.begin ? lhs.end > rhs.begin\n                                         : rhs.end > lhs.begin;\n        };\n\n        if (not overlaps(display_buffer.range(), range))\n            return;\n\n        const auto faces = m_faces | transform([&faces = context.context.faces()](auto&& spec) {\n                return faces[spec.second];\n            }) | gather<Vector<Face>>();\n\n        const auto& matches = get_matches(context.context.buffer(), display_buffer.range(), range);\n        kak_assert(matches.size() % m_faces.size() == 0);\n        for (size_t m = 0; m < matches.size(); ++m)\n        {\n            auto& face = faces[m % faces.size()];\n            if (face == Face{})\n                continue;\n\n            highlight_range(display_buffer,\n                            matches[m].begin, matches[m].end,\n                            false, apply_face(face));\n        }\n    }\n\n    void reset(Regex regex, FacesSpec faces)\n    {\n        m_regex = std::move(regex);\n        m_faces = std::move(faces);\n        ensure_first_face_is_capture_0();\n        ++m_regex_version;\n    }\n\n    static UniquePtr<Highlighter> create(HighlighterParameters params, Highlighter*)\n    {\n        if (params.size() < 2)\n            throw runtime_error(\"wrong parameter count\");\n\n        Regex re{params[0], RegexCompileFlags::Optimize};\n\n        FacesSpec faces;\n        for (auto& spec : params.subrange(1))\n        {\n            auto colon = find(spec, ':');\n            if (colon == spec.end())\n                throw runtime_error(format(\"wrong face spec: '{}' expected <capture>:<facespec>\", spec));\n            const StringView capture_name{spec.begin(), colon};\n            const int capture = str_to_int_ifp(capture_name).value_or_compute([&] {\n                return re.named_capture_index(capture_name);\n            });\n            if (capture < 0)\n                throw runtime_error(format(\"capture name {} is neither a capture index, nor an existing capture name\",\n                                           capture_name));\n            faces.emplace_back(capture, parse_face({colon+1, spec.end()}));\n        }\n\n        return make_unique_ptr<RegexHighlighter>(std::move(re), std::move(faces));\n    }\n\nprivate:\n    // stores the range for each highlighted capture of each match\n    using MatchList = Vector<BufferRange, MemoryDomain::Highlight>;\n    struct Cache\n    {\n        size_t m_timestamp = -1;\n        size_t m_regex_version = -1;\n        struct RangeAndMatches { BufferRange range; MatchList matches; };\n        using RangeAndMatchesList = Vector<RangeAndMatches, MemoryDomain::Highlight>;\n        HashMap<BufferRange, RangeAndMatchesList, MemoryDomain::Highlight> m_matches;\n    };\n    BufferSideCache<Cache> m_cache;\n\n    Regex     m_regex;\n    FacesSpec m_faces;\n\n    size_t m_regex_version = 0;\n\n    void ensure_first_face_is_capture_0()\n    {\n        if (m_faces.empty())\n            return;\n\n        std::sort(m_faces.begin(), m_faces.end(),\n                  [](auto&& lhs, auto&& rhs) { return lhs.first < rhs.first; });\n        if (m_faces[0].first != 0)\n            m_faces.emplace(m_faces.begin(), 0, FaceSpec{});\n    }\n\n    void add_matches(const Buffer& buffer, MatchList& matches, BufferRange range)\n    {\n        kak_assert(matches.size() % m_faces.size() == 0);\n        for (auto&& match : RegexIterator{get_iterator(buffer, range.begin),\n                                          get_iterator(buffer, range.end),\n                                          buffer.begin(), buffer.end(), m_regex,\n                                          match_flags(is_bol(range.begin),\n                                                      is_eol(buffer, range.end),\n                                                      is_bow(buffer, range.begin),\n                                                      is_eow(buffer, range.end))})\n        {\n            for (auto& face : m_faces)\n            {\n                const auto& sub = match[face.first];\n                matches.push_back({sub.first.coord(), sub.second.coord()});\n            }\n        }\n    }\n\n    const MatchList& get_matches(const Buffer& buffer, BufferRange display_range, BufferRange buffer_range)\n    {\n        Cache& cache = m_cache.get(buffer);\n\n        if (cache.m_regex_version != m_regex_version or\n            cache.m_timestamp != buffer.timestamp() or\n            accumulate(cache.m_matches, (size_t)0, [](size_t c, auto&& m) { return c + m.value.size(); }) > 1000)\n        {\n            cache.m_matches.clear();\n            cache.m_timestamp = buffer.timestamp();\n            cache.m_regex_version = m_regex_version;\n        }\n\n        auto& matches = cache.m_matches[buffer_range];\n\n        const LineCount line_offset = 3;\n        BufferRange range{std::max<BufferCoord>(buffer_range.begin, display_range.begin.line - line_offset),\n                          std::min<BufferCoord>(buffer_range.end, display_range.end.line + line_offset)};\n\n        auto it = std::upper_bound(matches.begin(), matches.end(), range.begin,\n                                   [](const BufferCoord& lhs, const Cache::RangeAndMatches& rhs)\n                                   { return lhs < rhs.range.end; });\n\n        if (it == matches.end() or it->range.begin > range.end)\n        {\n            it = matches.insert(it, Cache::RangeAndMatches{range, {}});\n            add_matches(buffer, it->matches, range);\n        }\n        else if (it->matches.empty())\n        {\n            it->range = range;\n            add_matches(buffer, it->matches, range);\n        }\n        else\n        {\n            // Here we extend the matches, that is not strictly valid,\n            // but may work nicely with every reasonable regex, and\n            // greatly reduces regex parsing. To change if we encounter\n            // regex that do not work great with that.\n            BufferRange& old_range = it->range;\n            MatchList& matches = it->matches;\n\n            // Thanks to the ensure_first_face_is_capture_0 method, we know\n            // these point to the first/last matches capture 0.\n            auto first_end = matches.begin()->end;\n            auto last_end = (matches.end() - m_faces.size())->end;\n\n            // add regex matches from new begin to old first match end\n            if (range.begin < old_range.begin)\n            {\n                matches.erase(matches.begin(), matches.begin() + m_faces.size());\n                size_t pivot = matches.size();\n                old_range.begin = range.begin;\n                add_matches(buffer, matches, {range.begin, first_end});\n\n                std::rotate(matches.begin(), matches.begin() + pivot, matches.end());\n            }\n            // add regex matches from old last match begin to new end\n            if (old_range.end < range.end)\n            {\n                old_range.end = range.end;\n                add_matches(buffer, matches, {last_end, range.end});\n            }\n        }\n        return it->matches;\n    }\n};\n\ntemplate<typename RegexGetter, typename FaceGetter>\nclass DynamicRegexHighlighter : public Highlighter\n{\npublic:\n    DynamicRegexHighlighter(RegexGetter get_regex, FaceGetter resolve_faces)\n      : Highlighter{HighlightPass::Colorize},\n        m_get_regex(std::move(get_regex)),\n        m_resolve_faces(std::move(resolve_faces)),\n        m_highlighter(Regex{}, FacesSpec{}) {}\n\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override\n    {\n        Regex regex = m_get_regex(context.context);\n        FacesSpec face = regex.empty() ? FacesSpec{} : m_resolve_faces(regex);\n        if (regex != m_last_regex or face != m_last_face)\n        {\n            m_last_regex = std::move(regex);\n            m_last_face = face;\n            if (not m_last_regex.empty())\n                m_highlighter.reset(m_last_regex, m_last_face);\n        }\n        if (not m_last_regex.empty() and not m_last_face.empty())\n            m_highlighter.highlight(context, display_buffer, range);\n    }\n\nprivate:\n    Regex       m_last_regex;\n    RegexGetter m_get_regex;\n\n    FacesSpec   m_last_face;\n    FaceGetter  m_resolve_faces;\n\n    RegexHighlighter m_highlighter;\n};\n\nconst HighlighterDesc dynamic_regex_desc = {\n    \"Parameters: <expr> <capture num>:<face> <capture num>:<face>...\\n\"\n    \"Evaluate expression at every redraw to gather a regex\",\n    {}\n};\nUniquePtr<Highlighter> create_dynamic_regex_highlighter(HighlighterParameters params, Highlighter*)\n{\n    if (params.size() < 2)\n        throw runtime_error(\"wrong parameter count\");\n\n    Vector<std::pair<String, FaceSpec>> faces;\n    for (auto& spec : params.subrange(1))\n    {\n        auto colon = find(spec, ':');\n        if (colon == spec.end())\n            throw runtime_error(\"wrong face spec: '\" + spec +\n                                 \"' expected <capture>:<facespec>\");\n        faces.emplace_back(String{spec.begin(), colon}, parse_face({colon+1, spec.end()}));\n    }\n\n    auto make_hl = [](auto& get_regex, auto& resolve_faces) {\n        return make_unique_ptr<DynamicRegexHighlighter<std::remove_cvref_t<decltype(get_regex)>,\n                                                        std::remove_cvref_t<decltype(resolve_faces)>>>(\n            std::move(get_regex), std::move(resolve_faces));\n    };\n    auto resolve_faces = [faces=std::move(faces)](const Regex& regex){\n        FacesSpec spec;\n        for (auto& face : faces)\n        {\n            const int capture = str_to_int_ifp(face.first).value_or_compute([&] {\n                return regex.named_capture_index(face.first);\n            });\n            if (capture < 0)\n            {\n                write_to_debug_buffer(format(\"Error while evaluating dynamic regex expression faces,\"\n                                             \" {} is neither a capture index nor a capture name\",\n                                             face.first));\n                return FacesSpec{};\n            }\n            spec.emplace_back(capture, std::move(face.second));\n        }\n        return spec;\n    };\n\n    CommandParser parser{params[0]};\n    auto token = parser.read_token(true);\n    if (token and parser.done() and token->type == Token::Type::OptionExpand and\n        GlobalScope::instance().options()[token->content].is_of_type<Regex>())\n    {\n        auto get_regex = [option_name = token->content](const Context& context) {\n            return context.options()[option_name].get<Regex>();\n        };\n        return make_hl(get_regex, resolve_faces);\n    }\n\n    auto get_regex = [expr = params[0]](const Context& context){\n        try\n        {\n            auto re = expand(expr, context);\n            return re.empty() ? Regex{} : Regex{re};\n        }\n        catch (runtime_error& err)\n        {\n            write_to_debug_buffer(format(\"Error while evaluating dynamic regex expression: {}\", err.what()));\n            return Regex{};\n        }\n    };\n    return make_hl(get_regex, resolve_faces);\n}\n\nconst HighlighterDesc line_desc = {\n    \"Parameters: <value string> <face>\\n\"\n    \"Highlight the line given by evaluating <value string> with <face>\",\n    {}\n};\nUniquePtr<Highlighter> create_line_highlighter(HighlighterParameters params, Highlighter*)\n{\n    if (params.size() != 2)\n        throw runtime_error(\"wrong parameter count\");\n\n    auto func = [line_expr=params[0], facespec=parse_face(params[1])]\n                (HighlightContext context, DisplayBuffer& display_buffer, BufferRange)\n    {\n        LineCount line = -1;\n        try\n        {\n            line = str_to_int_ifp(expand(line_expr, context.context)).value_or(0) - 1;\n        }\n        catch (runtime_error& err)\n        {\n            write_to_debug_buffer(\n                format(\"Error evaluating highlight line expression: {}\", err.what()));\n        }\n\n        if (line < 0)\n            return;\n\n        auto it = find_if(display_buffer.lines(),\n                          [line](const DisplayLine& l)\n                          { return l.range().begin.line == line; });\n        if (it == display_buffer.lines().end())\n            return;\n\n        auto face = context.context.faces()[facespec];\n        ColumnCount column = 0;\n        for (auto& atom : *it)\n        {\n            column += atom.length();\n            if (atom.has_buffer_range() and atom.begin().line != line)\n                break;\n            apply_face(face)(atom);\n        }\n        const ColumnCount remaining = context.context.window().dimensions().column - column;\n        if (remaining > 0)\n            it->push_back({String{' ', remaining}, face});\n    };\n\n    return make_highlighter(std::move(func));\n}\n\nconst HighlighterDesc column_desc = {\n    \"Parameters: [-ruler <character>] <column> <face>\\n\"\n    \"Highlight the column <column> with <face>\",\n    {\n        { { \"ruler\", { ArgCompleter{}, \"replace empty or whitespace cells with the given character. When provided, <face> is not applied to non-empty cells\" } } },\n        ParameterDesc::Flags::None, 2, 2\n    },\n};\nUniquePtr<Highlighter> create_column_highlighter(HighlighterParameters params, Highlighter*)\n{\n    ParametersParser parser{params, column_desc.params};\n\n    auto positionals = parser.positionals_from(0);\n\n    auto ruler = parser.get_switch(\"ruler\");\n    bool highlight_non_blank = not (bool)ruler;\n    auto col_char = ruler.value_or(\" \").str();\n    if (col_char.char_length() > 1)\n        throw runtime_error(\"-ruler expects a single character\");\n\n    auto func = [col_expr=positionals[0], facespec=parse_face(positionals[1]), highlight_non_blank, col_char]\n                (HighlightContext context, DisplayBuffer& display_buffer, BufferRange)\n    {\n        ColumnCount column = -1;\n        try\n        {\n            column = str_to_int_ifp(expand(col_expr, context.context)).value_or(0) - 1;\n        }\n        catch (runtime_error& err)\n        {\n            write_to_debug_buffer(\n                format(\"Error evaluating highlight column expression: {}\", err.what()));\n        }\n\n        if (column < 0)\n            return;\n\n        const auto face = context.context.faces()[facespec];\n        if (column < context.setup.first_column or column >= context.setup.first_column + context.context.window().dimensions().column)\n            return;\n\n        column += context.setup.widget_columns - context.setup.first_column;\n        for (auto& line : display_buffer.lines())\n        {\n            auto remaining_col = column;\n            bool found = false;\n            for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it)\n            {\n                const auto atom_len = atom_it->length();\n                if (remaining_col < atom_len)\n                {\n                    if (remaining_col > 0)\n                        atom_it = ++line.split(atom_it, remaining_col);\n                    if (atom_it->length() > 1)\n                        atom_it = line.split(atom_it, 1_col);\n                    if (highlight_non_blank)\n                        atom_it->face = merge_faces(atom_it->face, face);\n                    else if (is_blank(atom_it->content()[0]))\n                    {\n                        atom_it->replace(col_char);\n                        atom_it->face = merge_faces(atom_it->face, face);\n                    }\n                    found = true;\n                    break;\n                }\n                remaining_col -= atom_len;\n            }\n            if (found)\n                continue;\n\n            if (remaining_col > 0)\n                line.push_back({String{' ', remaining_col}, Face{}});\n            line.push_back({col_char, face});\n        }\n    };\n\n    return make_highlighter(std::move(func));\n}\n\nconst HighlighterDesc wrap_desc = {\n    \"Parameters: [-word] [-indent] [-width <max_width>] [-marker <marker_text>]\\n\"\n    \"Wrap lines to window width\",\n    { {\n        { \"word\",   { {}, \"wrap at word boundaries instead of codepoint boundaries\" } },\n        { \"indent\", { {}, \"preserve line indentation of the wrapped line\" } },\n        { \"width\",  { ArgCompleter{}, \"wrap at the given column instead of the window's width\" } },\n        { \"marker\", { ArgCompleter{}, \"insert the given text at the beginning of the wrapped line\" } }, },\n        ParameterDesc::Flags::None, 0, 0\n    }\n};\nstruct WrapHighlighter : Highlighter\n{\n    WrapHighlighter(ColumnCount max_width, bool word_wrap, bool preserve_indent, String marker)\n        : Highlighter{HighlightPass::Wrap}, m_word_wrap{word_wrap},\n          m_preserve_indent{preserve_indent}, m_max_width{max_width},\n          m_marker{std::move(marker)} {}\n\n    static constexpr StringView ms_id = \"wrap\";\n\n    struct SplitPos{ DisplayLine::iterator atom_it; ByteCount byte; ColumnCount column; };\n\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override\n    {\n        if (contains(context.disabled_ids, ms_id))\n            return;\n\n        const ColumnCount wrap_column = std::min(m_max_width, context.context.window().dimensions().column - context.setup.widget_columns);\n        if (wrap_column <= 0)\n            return;\n\n        const Buffer& buffer = context.context.buffer();\n        const int tabstop = context.context.options()[\"tabstop\"].get<int>();\n        const ColumnCount marker_len = zero_if_greater(m_marker.column_length(), wrap_column);\n        const Face face_marker = context.context.faces()[\"WrapMarker\"];\n        for (auto it = display_buffer.lines().begin();\n             it != display_buffer.lines().end(); ++it)\n        {\n            const ColumnCount indent = m_preserve_indent ?\n                zero_if_greater(line_indent(buffer, tabstop, it->range().begin.line), wrap_column) : 0_col;\n            const ColumnCount prefix_len = std::max(marker_len, indent);\n\n            SplitPos pos{it->begin(), 0, 0}; ;\n            while (next_split_pos(pos, it->end(), wrap_column, prefix_len))\n            {\n                auto& line = *it;\n\n                if (pos.byte > 0)\n                    pos.atom_it = ++line.split(pos.atom_it, pos.atom_it->begin() + BufferCoord{0, pos.byte});\n\n                auto coord = pos.atom_it->begin();\n                DisplayLine new_line{AtomList{std::make_move_iterator(pos.atom_it), std::make_move_iterator(line.end())}};\n                line.erase(pos.atom_it, line.end());\n                auto next_atom_it = new_line.begin();\n\n                if (marker_len != 0)\n                    next_atom_it = ++new_line.insert(new_line.begin(), {m_marker, face_marker});\n                if (indent > marker_len)\n                {\n                    next_atom_it = new_line.insert(next_atom_it, {buffer, {coord, coord}});\n                    next_atom_it->replace(String{' ', indent - marker_len});\n                    ++next_atom_it;\n                }\n\n                it = display_buffer.lines().insert(it+1, std::move(new_line));\n                pos = SplitPos{next_atom_it, 0, std::max(marker_len, indent)};\n            }\n        }\n    }\n\n    void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const override\n    {\n        if (contains(context.disabled_ids, ms_id))\n            return;\n\n        const ColumnCount wrap_column = std::min(m_max_width, context.context.window().dimensions().column - setup.widget_columns);\n        if (wrap_column <= 0)\n            return;\n\n        // Disable horizontal scrolling when using a WrapHighlighter\n        setup.first_column = 0;\n        setup.scroll_offset.column = 0;\n    }\n\n    void fill_unique_ids(Vector<StringView>& unique_ids) const override\n    {\n        unique_ids.push_back(ms_id);\n    }\n\n    bool next_split_pos(SplitPos& pos, DisplayLine::iterator line_end,\n                        ColumnCount wrap_column, ColumnCount prefix_len) const\n    {\n        SplitPos last_word_boundary{};\n        SplitPos last_WORD_boundary{};\n\n        auto update_word_boundaries = [&](Codepoint cp) {\n            if (m_word_wrap and not is_word<Word>(cp))\n                last_word_boundary = pos;\n            if (m_word_wrap and not is_word<WORD>(cp))\n                last_WORD_boundary = pos;\n        };\n\n        if (pos.atom_it->type() != DisplayAtom::Range and pos.atom_it->length() > wrap_column)\n        {\n            ++pos.atom_it;\n            return true;\n        }\n\n        while (pos.atom_it != line_end and pos.column < wrap_column)\n        {\n            if (pos.atom_it->type() != DisplayAtom::Range and pos.column + pos.atom_it->length() > wrap_column)\n                return true;\n\n            auto content = pos.atom_it->content();\n            const char* it = &content[pos.byte];\n            const Codepoint cp = utf8::read_codepoint(it, content.end());\n            const ColumnCount width = codepoint_width(cp);\n            if (pos.column + width > wrap_column) // the target column was in the char\n            {\n                update_word_boundaries(cp);\n                break;\n            }\n            pos.column += width;\n            pos.byte = (int)(it - content.begin());\n            update_word_boundaries(cp);\n            if (it == content.end())\n            {\n                ++pos.atom_it;\n                pos.byte = 0;\n                // Thanks to the pass ordering, atom boundary should always be reasonable word split points\n                last_word_boundary = pos;\n                last_WORD_boundary = pos;\n            }\n        }\n        if (pos.atom_it == line_end)\n            return false;\n\n        auto content = pos.atom_it->content();\n        if (m_word_wrap and pos.byte < content.length())\n        {\n            auto find_split_pos = [&](SplitPos start_pos, auto is_word) {\n                if (start_pos.column == 0)\n                    return false;\n                const char* it = &content[pos.byte];\n                // split at current position if is a word boundary\n                if (not is_word(utf8::codepoint(it, content.end()), {'_'}))\n                    return true;\n                // split at last word boundary if the word is shorter than our wrapping width\n                ColumnCount word_length = pos.column - start_pos.column;\n                ColumnCount max_word_length = wrap_column - prefix_len;\n                while (it != content.end() and word_length <= max_word_length)\n                {\n                    const Codepoint cp = utf8::read_codepoint(it, content.end());\n                    if (not is_word(cp, {'_'}))\n                        break;\n                    word_length += codepoint_width(cp);\n                }\n                if (word_length <= max_word_length)\n                {\n                    pos = start_pos;\n                    return true;\n                }\n                return false;\n            };\n            if (find_split_pos(last_WORD_boundary, is_word<WORD>) or\n                find_split_pos(last_word_boundary, is_word<Word>))\n                return true;\n        }\n\n        return true;\n    }\n\n    static ColumnCount line_indent(const Buffer& buffer, int tabstop, LineCount line)\n    {\n        StringView l = buffer[line];\n        auto col = 0_byte;\n        while (is_horizontal_blank(l[col]))\n               ++col;\n        return get_column(buffer, tabstop, {line, col});\n    }\n\n    static UniquePtr<Highlighter> create(HighlighterParameters params, Highlighter*)\n    {\n        ParametersParser parser(params, wrap_desc.params);\n        ColumnCount max_width = parser.get_switch(\"width\").map(str_to_int)\n            .value_or(std::numeric_limits<int>::max());\n\n        return make_unique_ptr<WrapHighlighter>(max_width, (bool)parser.get_switch(\"word\"),\n                                                 (bool)parser.get_switch(\"indent\"),\n                                                 parser.get_switch(\"marker\").value_or(\"\").str());\n    }\n\n    static ColumnCount zero_if_greater(ColumnCount val, ColumnCount max) { return val < max ? val : 0; };\n\n    const bool m_word_wrap;\n    const bool m_preserve_indent;\n    const ColumnCount m_max_width;\n    const String m_marker;\n};\n\nstruct TabulationHighlighter : Highlighter\n{\n    TabulationHighlighter() : Highlighter{HighlightPass::Replace} {}\n\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override\n    {\n        const ColumnCount tabstop = context.context.options()[\"tabstop\"].get<int>();\n        const auto& buffer = context.context.buffer();\n        for (auto& line : display_buffer.lines())\n        {\n            ColumnCount column = 0;\n            const char* line_data = nullptr;\n            const char* pos = nullptr;\n            for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it)\n            {\n                if (atom_it->type() != DisplayAtom::Range)\n                    continue;\n\n                auto begin = atom_it->begin();\n                if (auto* atom_line_data = buffer[begin.line].data(); atom_line_data != line_data)\n                {\n                    pos = line_data = atom_line_data;\n                    column = 0;\n                }\n\n                kak_assert(pos != nullptr and pos <= line_data + begin.column);\n                for (auto end = line_data + atom_it->end().column; pos != end; ++pos)\n                {\n                    const char* next_tab = std::find(pos, end, '\\t');\n                    if (next_tab == end)\n                    {\n                        pos = end;\n                        break;\n                    }\n\n                    while (pos != next_tab)\n                        column += codepoint_width(utf8::read_codepoint(pos, next_tab));\n                    const ColumnCount tabwidth = tabstop - (column % tabstop);\n                    column += tabwidth;\n\n                    if (pos >= line_data + atom_it->begin().column)\n                    {\n                        if (pos != line_data + atom_it->begin().column)\n                            atom_it = ++line.split(atom_it, {begin.line, ByteCount(pos - line_data)});\n                        if (pos + 1 != end)\n                            atom_it = line.split(atom_it, {begin.line, ByteCount(pos + 1 - line_data)});\n\n                        atom_it->replace(String{' ', tabwidth});\n                        ++atom_it;\n                    }\n                }\n\n                if (atom_it == line.end())\n                    break;\n            }\n        }\n    }\n\n    void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const override\n    {\n        auto& buffer = context.context.buffer();\n        // Ensure that a cursor on a tab character makes the full tab character visible\n        auto cursor = context.context.selections().main().cursor();\n        if (buffer.byte_at(cursor) != '\\t')\n            return;\n\n        const ColumnCount tabstop = context.context.options()[\"tabstop\"].get<int>();\n        const ColumnCount column = get_column(buffer, tabstop, cursor);\n        const ColumnCount width = tabstop - (column % tabstop);\n        const ColumnCount win_end = setup.first_column + context.context.window().dimensions().column - setup.widget_columns;\n        const ColumnCount offset = std::max(column + width - win_end, 0_col);\n\n        setup.first_column += offset;\n    }\n};\n\nconst HighlighterDesc show_whitespace_desc = {\n    \"Parameters: [-tab <separator>] [-tabpad <separator>] [-lf <separator>] [-spc <separator>] [-nbsp <separator>] [-indent <separator>]\\n\"\n    \"Display whitespaces using symbols\",\n    { {\n        { \"tab\",    { ArgCompleter{}, \"replace tabulations with the given character\" } },\n        { \"tabpad\", { ArgCompleter{}, \"append as many of the given character as is necessary to honor `tabstop`\" } },\n        { \"spc\",    { ArgCompleter{}, \"replace spaces with the given character\" } },\n        { \"lf\",     { ArgCompleter{}, \"replace line feeds with the given character\" } },\n        { \"nbsp\",   { ArgCompleter{}, \"replace non-breakable spaces with the given character\" } },\n        { \"indent\", { ArgCompleter{}, \"replace first space of every indent with the given character according to `indentwidth`\" } },\n        { \"only-trailing\", { {}, \"only highlighting trailing whitespaces\" } } },\n        ParameterDesc::Flags::None, 0, 0\n    }\n};\nstruct ShowWhitespacesHighlighter : Highlighter\n{\n    ShowWhitespacesHighlighter(String tab, String tabpad, String spc, String lf, String nbsp, String indent, bool only_trailing)\n      : Highlighter{HighlightPass::Replace}, m_tab{std::move(tab)}, m_tabpad{std::move(tabpad)},\n        m_spc{std::move(spc)}, m_lf{std::move(lf)}, m_nbsp{std::move(nbsp)}, m_indent{std::move(indent)}, m_only_trailing{std::move(only_trailing)}\n    {}\n\n    static UniquePtr<Highlighter> create(HighlighterParameters params, Highlighter*)\n    {\n        ParametersParser parser(params, show_whitespace_desc.params);\n\n        bool only_trailing = (bool) parser.get_switch(\"only-trailing\");\n        auto get_param = [&](StringView param,  StringView fallback) {\n            StringView value = parser.get_switch(param).value_or(fallback);\n            if (value.char_length() > 1)\n                throw runtime_error{format(\"-{} expects a single character or empty parameter\", param)};\n            return value.str();\n        };\n\n        return make_unique_ptr<ShowWhitespacesHighlighter>(\n            get_param(\"tab\", \"→\"), get_param(\"tabpad\", \" \"), get_param(\"spc\", \"·\"),\n            get_param(\"lf\", \"¬\"), get_param(\"nbsp\", \"⍽\"), get_param(\"indent\", \"│\"), only_trailing);\n    }\n\nprivate:\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override\n    {\n        const int tabstop = context.context.options()[\"tabstop\"].get<int>();\n        const int indentwidth = context.context.options()[\"indentwidth\"].get<int>();\n        auto whitespaceface = context.context.faces()[\"Whitespace\"];\n        auto indentface = context.context.faces()[\"WhitespaceIndent\"];\n        const auto& buffer = context.context.buffer();\n        for (auto& line : display_buffer.lines())\n        {\n            bool is_indentation = true;\n            for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it)\n            {\n                if (atom_it->type() != DisplayAtom::Range)\n                    continue;\n\n                auto begin = get_iterator(buffer, atom_it->begin());\n                auto end = get_iterator(buffer, atom_it->end());\n                auto last_non_space = begin.coord();\n\n                auto is_whitespace = [](Codepoint cp) {\n                    return cp == '\\t' or cp == ' ' or cp == '\\n' or cp == 0xA0 or cp == 0x202F;\n                };\n\n                if(m_only_trailing)\n                {\n                    for (BufferIterator it = begin; it != end; )\n                    {\n                        if (not is_whitespace(utf8::read_codepoint(it, end)))\n                            last_non_space = it.coord();\n                    }\n                }\n\n                for (BufferIterator it = begin; it != end; )\n                {\n                    auto coord = it.coord();\n                    Codepoint cp = utf8::read_codepoint(it, end);\n                    auto face = whitespaceface;\n                    if (is_whitespace(cp))\n                    {\n                        if (m_only_trailing and it.coord() <= last_non_space)\n                            continue;\n\n                        if (coord != begin.coord())\n                            atom_it = ++line.split(atom_it, coord);\n                        if (it != end)\n                            atom_it = line.split(atom_it, it.coord());\n\n                        if (cp == '\\t' and not m_tab.empty() and not m_tabpad.empty())\n                        {\n                            const ColumnCount column = get_column(buffer, tabstop, coord);\n                            const ColumnCount count = tabstop - (column % tabstop);\n                            atom_it->replace(m_tab + String(m_tabpad[(CharCount)0], count - m_tab.column_length()));\n                        }\n                        else if (cp == ' ' and is_indentation and indentwidth > 0 and not m_indent.empty()) {\n                            const ColumnCount column = get_column(buffer, tabstop, coord);\n                            if (column % indentwidth == 0 and column != 0) {\n                                atom_it->replace(m_indent);\n                                face = indentface;\n                            }\n                            else {\n                                atom_it->replace(m_spc);\n                            }\n                        }\n                        else if (cp == ' ' and not m_spc.empty()) {\n                            atom_it->replace(m_spc);\n                        }\n                        else if (cp == '\\n' and not m_lf.empty())\n                            atom_it->replace(m_lf);\n                        else if ((cp == 0xA0 or cp == 0x202F) and not m_nbsp.empty())\n                            atom_it->replace(m_nbsp);\n                        atom_it->face = merge_faces(atom_it->face, face);\n                        break;\n                    }\n                    else\n                    {\n                        is_indentation = false;\n                    }\n                }\n            }\n        }\n    }\n\n    const String m_tab, m_tabpad, m_spc, m_lf, m_nbsp, m_indent;\n    const bool m_only_trailing;\n};\n\nconst HighlighterDesc line_numbers_desc = {\n    \"Parameters: [-relative] [-hlcursor] [-separators <separator|separator:cursor|cursor:up:down>] [-min-digits <cols>]\\n\"\n    \"Display line numbers\",\n    { {\n        { \"relative\", { {}, \"show line numbers relative to the main cursor line\" } },\n        { \"full-relative\", { {}, \"show line numbers relative to the main cursor line and the main cursor line as 0\" } },\n        { \"separator\", { ArgCompleter{}, \"string to separate the line numbers column from the rest of the buffer (default '|')\" } },\n        { \"cursor-separator\", { ArgCompleter{}, \"identical to -separator but applies only to the line of the cursor (default is the same value passed to -separator)\" } },\n        { \"min-digits\", { ArgCompleter{}, \"use at least the given number of columns to display line numbers (default 2)\" } },\n        { \"hlcursor\", { {}, \"highlight the cursor line with a separate face\" } } },\n        ParameterDesc::Flags::None, 0, 0\n    }\n};\nstruct LineNumbersHighlighter : Highlighter\n{\n    LineNumbersHighlighter(bool relative, bool zero_cursor_line, bool hl_cursor_line, String separator, String cursor_separator, int min_digits)\n      : Highlighter{HighlightPass::Move},\n        m_relative{relative},\n        m_zero_cursor_line{zero_cursor_line},\n        m_hl_cursor_line{hl_cursor_line},\n        m_separator{std::move(separator)},\n        m_cursor_separator{std::move(cursor_separator)},\n        m_min_digits{min_digits} {}\n\n    static UniquePtr<Highlighter> create(HighlighterParameters params, Highlighter*)\n    {\n        ParametersParser parser(params, line_numbers_desc.params);\n\n        StringView separator = parser.get_switch(\"separator\").value_or(\"│\");\n        StringView cursor_separator = parser.get_switch(\"cursor-separator\").value_or(separator);\n\n        if (separator.length() > 10)\n            throw runtime_error(\"separator length is limited to 10 bytes\");\n\n        if (cursor_separator.column_length() != separator.column_length())\n            throw runtime_error(\"separator for active line should have the same length as 'separator'\");\n\n        int min_digits = parser.get_switch(\"min-digits\").map(str_to_int).value_or(2);\n        if (min_digits < 0)\n            throw runtime_error(\"min digits must be positive\");\n        if (min_digits > 10)\n            throw runtime_error(\"min digits is limited to 10\");\n        const bool relative = (bool)parser.get_switch(\"relative\");\n        const bool full_relative = (bool)parser.get_switch(\"full-relative\");\n        return make_unique_ptr<LineNumbersHighlighter>(relative or full_relative, full_relative, (bool)parser.get_switch(\"hlcursor\"), separator.str(), cursor_separator.str(), min_digits);\n    }\n\nprivate:\n    static constexpr StringView ms_id = \"line-numbers\";\n\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override\n    {\n        if (contains(context.disabled_ids, ms_id))\n            return;\n\n        const auto& faces = context.context.faces();\n        const Face face = faces[\"LineNumbers\"];\n        const Face face_wrapped = faces[\"LineNumbersWrapped\"];\n        const Face face_absolute = faces[\"LineNumberCursor\"];\n        int digit_count = compute_digit_count(context);\n\n        char format[16];\n        format_to(format, \"\\\\{:{}}\", digit_count);\n        const int main_line = (int)context.context.selections().main().cursor().line + 1;\n        int last_line = -1;\n        for (auto& line : display_buffer.lines())\n        {\n            const int current_line = (int)line.range().begin.line + 1;\n            const bool is_cursor_line = main_line == current_line;\n            const int line_to_format = (m_relative and (not is_cursor_line or m_zero_cursor_line)) ?\n                                       current_line - main_line : current_line;\n            char buffer[16];\n            format_to(buffer, format, std::abs(line_to_format));\n            const auto atom_face = last_line == current_line ? face_wrapped :\n                ((m_hl_cursor_line and is_cursor_line) ? face_absolute : face);\n\n            const auto& separator = is_cursor_line && last_line != current_line\n                                    ? m_cursor_separator : m_separator;\n\n            line.insert(line.begin(), {buffer, atom_face});\n            line.insert(line.begin() + 1, {separator, face});\n\n            last_line = current_line;\n        }\n    }\n\n    void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const override\n    {\n        if (contains(context.disabled_ids, ms_id))\n            return;\n\n        ColumnCount width = compute_digit_count(context) + m_separator.column_length();\n        setup.widget_columns += width;\n    }\n\n    void fill_unique_ids(Vector<StringView>& unique_ids) const override\n    {\n        unique_ids.push_back(ms_id);\n    }\n\n    int compute_digit_count(const HighlightContext& context) const\n    {\n        int digit_count = 0;\n        auto cursor_line = context.context.selections().main().cursor().line + 1;\n        LineCount line_count = (m_relative and m_zero_cursor_line)\n            ? std::max(abs(context.setup.first_line - cursor_line),\n                       abs(context.setup.first_line + context.setup.line_count - cursor_line))\n            : context.context.buffer().line_count();\n        for (LineCount c = line_count; c > 0; c /= 10)\n            ++digit_count;\n        return std::max(digit_count, m_min_digits);\n    }\n\n    const bool m_relative;\n    const bool m_zero_cursor_line;\n    const bool m_hl_cursor_line;\n    const String m_separator;\n    const String m_cursor_separator;\n    const int m_min_digits;\n};\n\nconst HighlighterDesc show_matching_desc = {\n    \"Apply the MatchingChar face to the char matching the one under the cursor\",\n    { { { \"previous\", {} } }, ParameterDesc::Flags::SwitchesOnlyAtStart, 0, 0 }\n};\ntemplate<bool match_prev>\nvoid show_matching_char(HighlightContext context, DisplayBuffer& display_buffer, BufferRange)\n{\n    const Face face = context.context.faces()[\"MatchingChar\"];\n    const auto& matching_pairs = context.context.options()[\"matching_pairs\"].get<Vector<Codepoint, MemoryDomain::Options>>();\n    const auto range = display_buffer.range();\n    const auto& buffer = context.context.buffer();\n    for (auto& sel : context.context.selections())\n    {\n        auto pos = sel.cursor();\n        if (pos < range.begin or pos >= range.end)\n            continue;\n\n        utf8::iterator it{buffer.iterator_at(pos), buffer};\n        auto match = find(matching_pairs, *it);\n        bool matching_prev = match == matching_pairs.end() and match_prev;\n        if (matching_prev)\n            match = find(matching_pairs, *--it);\n\n        if (match == matching_pairs.end())\n            continue;\n\n        if (matching_prev)\n            highlight_range(display_buffer, it.base().coord(), (it+1).base().coord(),\n                            false, apply_face(face));\n\n        int level = 0;\n        if (((match - matching_pairs.begin()) % 2) == 0)\n        {\n            const Codepoint opening = *match;\n            const Codepoint closing = *(match+1);\n            while (it.base().coord() <= range.end)\n            {\n                if (*it == opening)\n                    ++level;\n                else if (*it == closing and --level == 0)\n                {\n                    highlight_range(display_buffer, it.base().coord(), (it+1).base().coord(),\n                                    false, apply_face(face));\n                    break;\n                }\n                ++it;\n            }\n        }\n        else if (pos > range.begin)\n        {\n            const Codepoint opening = *(match-1);\n            const Codepoint closing = *match;\n            while (true)\n            {\n                if (*it == closing)\n                    ++level;\n                else if (*it == opening and --level == 0)\n                {\n                    highlight_range(display_buffer, it.base().coord(), (it+1).base().coord(),\n                                    false, apply_face(face));\n                    break;\n                }\n                if (it.base().coord() <= range.begin)\n                    break;\n                --it;\n            }\n        }\n    }\n}\n\nUniquePtr<Highlighter> create_matching_char_highlighter(HighlighterParameters params, Highlighter*)\n{\n    ParametersParser parser{params, show_matching_desc.params};\n    return make_highlighter(parser.get_switch(\"previous\") ? show_matching_char<true> : show_matching_char<false>);\n}\n\nvoid highlight_selections(HighlightContext context, DisplayBuffer& display_buffer, BufferRange)\n{\n    const auto& buffer = context.context.buffer();\n    const auto& faces = context.context.faces();\n    const Face sel_faces[6] = {\n            faces[\"PrimarySelection\"], faces[\"SecondarySelection\"],\n            faces[\"PrimaryCursor\"],    faces[\"SecondaryCursor\"],\n            faces[\"PrimaryCursorEol\"], faces[\"SecondaryCursorEol\"],\n    };\n\n    const auto& selections = context.context.selections();\n    for (size_t i = 0; i < selections.size(); ++i)\n    {\n        auto& sel = selections[i];\n        const bool forward = sel.anchor() <= sel.cursor();\n        BufferCoord begin = forward ? sel.anchor() : buffer.char_next(sel.cursor());\n        BufferCoord end   = forward ? (BufferCoord)sel.cursor() : buffer.char_next(sel.anchor());\n\n        const bool primary = (i == selections.main_index());\n        highlight_range(display_buffer, begin, end, false,\n                        apply_face(sel_faces[primary ? 0 : 1]));\n    }\n    for (size_t i = 0; i < selections.size(); ++i)\n    {\n        auto& sel = selections[i];\n        const BufferCoord coord = sel.cursor();\n        const bool primary = (i == selections.main_index());\n        const bool eol = buffer[coord.line].length() - 1 == coord.column;\n        highlight_range(display_buffer, coord, buffer.char_next(coord), false,\n                        apply_face(sel_faces[2 + (eol ? 2 : 0) + (primary ? 0 : 1)]));\n    }\n}\n\nvoid expand_unprintable(HighlightContext context, DisplayBuffer& display_buffer, BufferRange)\n{\n    auto is_printable = [](Codepoint cp) { // follow musl iswprint set of printable characters, but with straightforward impl\n        if (cp < 0xff)\n            return cp == '\\n' or cp >= ' ';\n        else if (cp == 0x2028 or cp == 0x2029 or (cp >= 0xFFF9 and cp <= 0xFFFB))\n            return false;\n        return true;\n    };\n    const auto& buffer = context.context.buffer();\n    auto error = context.context.faces()[FaceSpec{{}, \"Error\"}];\n    for (auto& line : display_buffer.lines())\n    {\n        for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it)\n        {\n            if (atom_it->type() != DisplayAtom::Range)\n                continue;\n\n            auto begin = atom_it->begin();\n            auto line_data = buffer[begin.line].data();\n            for (auto it  = line_data + begin.column, end = line_data + atom_it->end().column; it < end;)\n            {\n                auto next = it;\n                Codepoint cp = utf8::read_codepoint(next, end);\n                if (not is_printable(cp))\n                {\n                    if (ByteCount pos(it - line_data); pos != begin.column)\n                        atom_it = ++line.split(atom_it, {begin.line, pos});\n                    if (ByteCount pos(next - line_data); pos < atom_it->end().column)\n                        atom_it = line.split(atom_it, {begin.line, pos});\n\n                    atom_it->replace(\"�\");\n                    atom_it->face = error;\n                    break;\n                }\n                it = next;\n            }\n        }\n    }\n}\n\nstatic void update_line_specs_ifn(const Buffer& buffer, LineAndSpecList& line_flags)\n{\n    if (line_flags.prefix == buffer.timestamp())\n        return;\n\n    auto& lines = line_flags.list;\n\n    auto modifs = compute_line_modifications(buffer, line_flags.prefix);\n    auto ins_pos = lines.begin();\n    for (auto it = lines.begin(); it != lines.end(); ++it)\n    {\n        auto& line = std::get<0>(*it); // that line is 1 based as it comes from user side\n        auto modif_it = std::upper_bound(modifs.begin(), modifs.end(), line-1,\n                                         [](const LineCount& l, const LineModification& c)\n                                         { return l < c.old_line; });\n        if (modif_it != modifs.begin())\n        {\n            auto& prev = *(modif_it-1);\n            if (line-1 < prev.old_line + prev.num_removed)\n                continue; // line removed\n\n            line += prev.diff();\n        }\n\n        if (ins_pos != it)\n            *ins_pos = std::move(*it);\n        ++ins_pos;\n    }\n    lines.erase(ins_pos, lines.end());\n    line_flags.prefix = buffer.timestamp();\n}\n\nvoid option_update(LineAndSpecList& opt, const Context& context)\n{\n    update_line_specs_ifn(context.buffer(), opt);\n}\n\nvoid option_list_postprocess(Vector<LineAndSpec, MemoryDomain::Options>& opt)\n{\n    std::sort(opt.begin(), opt.end(),\n              [](auto& lhs, auto& rhs)\n              { return std::get<0>(lhs) < std::get<0>(rhs); });\n}\n\nconst HighlighterDesc flag_lines_desc = {\n    \"Parameters: <face> <option name>\\n\"\n    \"Display flags specified in the line-spec option <option name> with <face>\",\n    {}\n};\nstruct FlagLinesHighlighter : Highlighter\n{\n    FlagLinesHighlighter(String option_name, String default_face, bool after)\n        : Highlighter{HighlightPass::Move},\n          m_option_name{std::move(option_name)},\n          m_default_face{std::move(default_face)},\n          m_after(after) {}\n\n    static UniquePtr<Highlighter> create(HighlighterParameters params, Highlighter*)\n    {\n        ParametersParser parser{params, {\n            {{\"after\", {{}, \"display at line end\" }}},\n            ParameterDesc::Flags::SwitchesOnlyAtStart, 2, 2\n        }};\n\n        const String& default_face = parser[0];\n        const String& option_name = parser[1];\n\n        // throw if wrong option type\n        GlobalScope::instance().options()[option_name].get<LineAndSpecList>();\n\n        return make_unique_ptr<FlagLinesHighlighter>(option_name, default_face, (bool)parser.get_switch(\"after\"));\n    }\n\nprivate:\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override\n    {\n        auto& line_flags = context.context.options()[m_option_name].get_mutable<LineAndSpecList>();\n        const auto& buffer = context.context.buffer();\n        update_line_specs_ifn(buffer, line_flags);\n\n        auto def_face = context.context.faces()[m_default_face];\n        Vector<DisplayLine> display_lines;\n        auto& lines = line_flags.list;\n        try\n        {\n            for (auto& line : lines)\n            {\n                display_lines.push_back(parse_display_line(std::get<1>(line), context.context.faces()));\n                for (auto& atom : display_lines.back())\n                    atom.face = merge_faces(def_face, atom.face);\n            }\n        }\n        catch (runtime_error& err)\n        {\n            write_to_debug_buffer(format(\"Error while evaluating line flag: {}\", err.what()));\n            return;\n        }\n\n        ColumnCount width = 0;\n        for (auto& l : display_lines)\n             width = std::max(width, l.length());\n        const DisplayAtom empty{String{' ', width}, def_face};\n        for (auto& line : display_buffer.lines())\n        {\n            int line_num = (int)line.range().begin.line + 1;\n            auto it = find_if(lines,\n                              [&](const LineAndSpec& l)\n                              { return std::get<0>(l) == line_num; });\n            if (m_after)\n            {\n                if (it != lines.end())\n                {\n                    DisplayLine& display_line = display_lines[it - lines.begin()];\n                    std::copy(std::make_move_iterator(display_line.begin()),\n                              std::make_move_iterator(display_line.end()),\n                              std::inserter(line, line.end()));\n                }\n                continue;\n            }\n            if (it == lines.end())\n                line.insert(line.begin(), empty);\n            else\n            {\n                DisplayLine& display_line = display_lines[it - lines.begin()];\n                DisplayAtom padding_atom{String(' ', width - display_line.length()), def_face};\n                auto it = std::copy(std::make_move_iterator(display_line.begin()),\n                                    std::make_move_iterator(display_line.end()),\n                                    std::inserter(line, line.begin()));\n\n                if (padding_atom.length() != 0)\n                    *it++ = std::move(padding_atom);\n            }\n        }\n    }\n\n    void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const override\n    {\n        if (m_after)\n            return;\n\n        auto& line_flags = context.context.options()[m_option_name].get_mutable<LineAndSpecList>();\n        const auto& buffer = context.context.buffer();\n        update_line_specs_ifn(buffer, line_flags);\n\n        ColumnCount width = 0;\n        try\n        {\n            for (auto& line : line_flags.list)\n                width = std::max(parse_display_line(std::get<1>(line), context.context.faces()).length(), width);\n        }\n        catch (runtime_error& err)\n        {\n            write_to_debug_buffer(format(\"Error while evaluating line flag: {}\", err.what()));\n            return;\n        }\n\n        setup.widget_columns += width;\n    }\n\n    String m_option_name;\n    String m_default_face;\n    bool m_after;\n};\n\nbool is_empty(const InclusiveBufferRange& range)\n{\n    return range.last < BufferCoord{0,0};\n}\n\nString option_to_string(InclusiveBufferRange range)\n{\n    if (is_empty(range))\n        return format(\"{}.{}+0\", range.first.line+1, range.first.column+1);\n    return format(\"{}.{},{}.{}\",\n                  range.first.line+1, range.first.column+1,\n                  range.last.line+1, range.last.column+1);\n}\n\nInclusiveBufferRange option_from_string(Meta::Type<InclusiveBufferRange>, StringView str)\n{\n    auto sep = find_if(str, [](char c){ return c == ',' or c == '+'; });\n    auto dot_beg = find(StringView{str.begin(), sep}, '.');\n    auto dot_end = find(StringView{sep, str.end()}, '.');\n\n    if (sep == str.end() or dot_beg == sep or\n        (*sep == ',' and dot_end == str.end()))\n        throw runtime_error(format(\"'{}' does not follow <line>.<column>,<line>.<column> or <line>.<column>+<len> format\", str));\n\n    const BufferCoord first{str_to_int({str.begin(), dot_beg}) - 1,\n                            str_to_int({dot_beg+1, sep}) - 1};\n\n    if (first.line < 0 or first.column < 0)\n        throw runtime_error(\"coordinates elements should be >= 1\");\n\n    if (*sep == '+')\n    {\n        auto len = str_to_int({sep+1, str.end()});\n        return {first, len == 0 ? BufferCoord{-1,-1} : BufferCoord{first.line, first.column + len - 1}};\n    }\n\n    const BufferCoord last{str_to_int({sep+1, dot_end}) - 1, str_to_int({dot_end+1, str.end()}) - 1};\n    if (last.line < 0 or last.column < 0)\n        throw runtime_error(\"coordinates elements should be >= 1\");\n\n    return { std::min(first, last), std::max(first, last) };\n}\n\ntemplate<typename OptionType, typename DerivedType, HighlightPass pass = HighlightPass::Colorize>\nstruct OptionBasedHighlighter : Highlighter\n{\n    OptionBasedHighlighter(String option_name)\n        : Highlighter{pass}\n        , m_option_name{std::move(option_name)} {}\n\n    static UniquePtr<Highlighter> create(HighlighterParameters params, Highlighter*)\n    {\n        if (params.size() != 1)\n            throw runtime_error(\"wrong parameter count\");\n\n        const String& option_name = params[0];\n        // throw if wrong option type\n        GlobalScope::instance().options()[option_name].get<OptionType>();\n\n        return make_unique_ptr<DerivedType>(option_name);\n    }\n\n    OptionType& get_option(const HighlightContext& context) const\n    {\n        return context.context.options()[m_option_name].template get_mutable<OptionType>();\n    }\n\nprivate:\n    const String m_option_name;\n};\n\nBufferCoord& get_first(RangeAndString& r) { return std::get<0>(r).first; }\nBufferCoord& get_last(RangeAndString& r) { return std::get<0>(r).last; }\n\nbool option_element_compare(RangeAndString const& lhs, RangeAndString const& rhs)\n{\n    return std::get<0>(lhs).first == std::get<0>(rhs).first ?\n        std::get<0>(lhs).last < std::get<0>(rhs).last\n      : std::get<0>(lhs).first < std::get<0>(rhs).first;\n}\n\nvoid option_list_postprocess(Vector<RangeAndString, MemoryDomain::Options>& opt)\n{\n    std::sort(opt.begin(), opt.end(), option_element_compare);\n}\n\nvoid option_update(RangeAndStringList& opt, const Context& context)\n{\n    update_ranges(context.buffer(), opt.prefix, opt.list);\n    opt.prefix = context.buffer().timestamp();\n}\n\nbool option_add_from_strings(Vector<RangeAndString, MemoryDomain::Options>& opt, ConstArrayView<String> strs)\n{\n    auto vec = option_from_strings(Meta::Type<Vector<RangeAndString, MemoryDomain::Options>>{}, strs);\n    if (vec.empty())\n        return false;\n    auto middle = opt.insert(opt.end(),\n                             std::make_move_iterator(vec.begin()),\n                             std::make_move_iterator(vec.end()));\n    std::sort(middle, opt.end(), option_element_compare);\n    std::inplace_merge(opt.begin(), middle, opt.end(), option_element_compare);\n    return true;\n}\n\nconst HighlighterDesc ranges_desc = {\n    \"Parameters: <option name>\\n\"\n    \"Use the range-specs option given as parameter to highlight buffer\\n\"\n    \"each spec is interpreted as a face to apply to the range\",\n    {}\n};\nstruct RangesHighlighter : OptionBasedHighlighter<RangeAndStringList, RangesHighlighter>\n{\n    using RangesHighlighter::OptionBasedHighlighter::OptionBasedHighlighter;\nprivate:\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override\n    {\n        auto& buffer = context.context.buffer();\n        auto& range_and_faces = get_option(context);\n        update_ranges(buffer, range_and_faces.prefix, range_and_faces.list);\n        range_and_faces.prefix = buffer.timestamp();\n\n        for (auto& [range, face] : range_and_faces.list)\n        {\n            try\n            {\n                if (buffer.is_valid(range.first) and (buffer.is_valid(range.last) and not buffer.is_end(range.last)))\n                    highlight_range(display_buffer, range.first, buffer.char_next(range.last), false,\n                                    apply_face(context.context.faces()[face]));\n            }\n            catch (runtime_error&)\n            {}\n        }\n    }\n};\n\nconst HighlighterDesc replace_ranges_desc = {\n    \"Parameters: <option name>\\n\"\n    \"Use the range-specs option given as parameter to highlight buffer\\n\"\n    \"each spec is interpreted as a display line to display in place of the range\",\n    {}\n};\nstruct ReplaceRangesHighlighter : OptionBasedHighlighter<RangeAndStringList, ReplaceRangesHighlighter, HighlightPass::Replace>\n{\n    using ReplaceRangesHighlighter::OptionBasedHighlighter::OptionBasedHighlighter;\nprivate:\n    static bool is_valid(Buffer& buffer, BufferCoord c)\n    {\n        return c.line >= 0 and c.column >= 0 and c.line < buffer.line_count() and c.column <= buffer[c.line].length();\n    }\n\n    static bool is_fully_selected(const SelectionList& sels, const InclusiveBufferRange& range)\n    {\n        auto it = std::lower_bound(sels.begin(), sels.end(), range.first, [](const Selection& s, const BufferCoord& c) { return s.max() < c; });\n        if (it == sels.end())\n            return true;\n        return it->min() > range.last or (it->min() <= range.first and it->max() >= range.last);\n    }\n\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override\n    {\n        auto& buffer = context.context.buffer();\n        auto& sels = context.context.selections();\n        auto& range_and_faces = get_option(context);\n        update_ranges(buffer, range_and_faces.prefix, range_and_faces.list);\n        range_and_faces.prefix = buffer.timestamp();\n\n        for (auto& [range, spec] : range_and_faces.list)\n        {\n            try\n            {\n                if (!is_valid(buffer, range.first) or (!is_empty(range) and !is_valid(buffer, range.last)) or !is_fully_selected(sels, range))\n                    continue;\n                auto end = is_empty(range) ? range.first : buffer.char_next(range.last);\n                replace_range(display_buffer, range.first, end,\n                              [&, range=BufferRange{range.first, end}]\n                              (DisplayLineList& lines, DisplayLineList::iterator line, DisplayLine::iterator pos){\n                                  auto it = spec.begin(), eol = find(spec, '\\n');\n                                  while (true)\n                                  {\n                                      for (auto& atom : parse_display_line({it, eol}, context.context.faces()))\n                                      {\n                                          atom.replace(range);\n                                          pos = ++line->insert(pos, std::move(atom));\n                                      }\n                                      if (eol == spec.end())\n                                          break;\n                                      auto remaining = line->extract(pos, line->end());\n                                      line = lines.insert(++line, remaining);\n                                      pos = line->begin();\n                                      it = ++eol;\n                                      eol = std::find(it, spec.end(), '\\n');\n                                  }\n                              });\n            }\n            catch (runtime_error&)\n            {}\n        }\n    }\n\n    void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const override\n    {\n        auto& buffer = context.context.buffer();\n        auto& sels = context.context.selections();\n        auto& range_and_faces = get_option(context);\n        update_ranges(buffer, range_and_faces.prefix, range_and_faces.list);\n        range_and_faces.prefix = buffer.timestamp();\n\n        for (auto& [range, spec] : range_and_faces.list)\n        {\n            if (!is_valid(buffer, range.first) or (!is_empty(range) and !is_valid(buffer, range.last)) or !is_fully_selected(sels, range))\n                continue;\n\n            auto last = is_empty(range) ? range.first : buffer.char_next(range.last);\n            if (range.first.line < setup.first_line and last.line >= setup.first_line)\n                setup.first_line = range.first.line;\n\n            if (last.line >= setup.first_line and\n                range.first.line <= setup.first_line + setup.line_count and\n                range.first.line != last.line)\n            {\n                auto added_count = std::count(spec.begin(), spec.end(), '\\n');\n                auto removed_count = last.line - range.first.line;\n                setup.line_count += removed_count - added_count;\n            }\n        }\n    }\n};\n\nHighlightPass parse_passes(StringView str)\n{\n    HighlightPass passes{};\n    for (auto pass : str | split<StringView>('|'))\n    {\n        if (pass == \"colorize\")\n            passes |= HighlightPass::Colorize;\n        else if (pass == \"move\")\n            passes |= HighlightPass::Move;\n        else if (pass == \"wrap\")\n            passes |= HighlightPass::Wrap;\n        else\n            throw runtime_error{format(\"invalid highlight pass: {}\", pass)};\n    }\n    if (passes == HighlightPass{})\n        throw runtime_error{\"no passes specified\"};\n\n    return passes;\n}\n\nconst HighlighterDesc higlighter_group_desc = {\n    \"Parameters: [-passes <passes>]\\n\"\n    \"Creates a group that can contain other highlighters\",\n    { {\n        { \"passes\", { ArgCompleter{}, \"flags(colorize|move|wrap) \"\n                                       \"kind of highlighters can be put in the group \"\n                                       \"(default colorize)\" } } },\n        ParameterDesc::Flags::SwitchesOnlyAtStart, 0, 0\n    }\n};\nUniquePtr<Highlighter> create_highlighter_group(HighlighterParameters params, Highlighter*)\n{\n    ParametersParser parser{params, higlighter_group_desc.params};\n    HighlightPass passes = parse_passes(parser.get_switch(\"passes\").value_or(\"colorize\"));\n\n    return make_unique_ptr<HighlighterGroup>(passes);\n}\n\nconst HighlighterDesc ref_desc = {\n    \"Parameters: [-passes <passes>] <path>\\n\"\n    \"Reference the highlighter at <path> in shared highlighters\",\n    { {\n        { \"passes\", { ArgCompleter{}, \"flags(colorize|move|wrap) \"\n                                      \"kind of highlighters that can be referenced \"\n                                      \"(default colorize)\" } } },\n        ParameterDesc::Flags::SwitchesOnlyAtStart, 1, 1\n    }\n};\nstruct ReferenceHighlighter : Highlighter\n{\n    ReferenceHighlighter(HighlightPass passes, String name)\n        : Highlighter{passes}, m_name{std::move(name)} {}\n\n    static UniquePtr<Highlighter> create(HighlighterParameters params, Highlighter*)\n    {\n        ParametersParser parser{params, ref_desc.params};\n        HighlightPass passes = parse_passes(parser.get_switch(\"passes\").value_or(\"colorize\"));\n        return make_unique_ptr<ReferenceHighlighter>(passes, parser[0]);\n    }\n\nprivate:\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override\n    {\n        static Vector<std::pair<StringView, BufferRange>> running_refs;\n        const std::pair<StringView, BufferRange> desc{m_name, range};\n        if (contains(running_refs, desc))\n            return write_to_debug_buffer(format(\"highlighting recursion detected with ref to {}\", m_name));\n\n        running_refs.push_back(desc);\n        auto pop_desc = OnScopeEnd([] { running_refs.pop_back(); });\n\n        try\n        {\n            SharedHighlighters::instance().get_child(m_name).highlight(context, display_buffer, range);\n        }\n        catch (child_not_found&)\n        {}\n    }\n\n    void do_compute_display_setup(HighlightContext context, DisplaySetup& setup) const override\n    {\n        try\n        {\n            SharedHighlighters::instance().get_child(m_name).compute_display_setup(context, setup);\n        }\n        catch (child_not_found&)\n        {}\n    }\n\n    const String m_name;\n};\n\nstruct RegexMatch\n{\n    LineCount line;\n    ByteCount begin;\n    ByteCount end;\n    uint16_t capture_pos;\n    uint16_t capture_len;\n\n    BufferCoord begin_coord() const { return { line, begin }; }\n    BufferCoord end_coord() const { return { line, end }; }\n    bool empty() const { return begin == end; }\n\n    StringView capture(const Buffer& buffer) const\n    {\n        if (capture_len == 0)\n            return {};\n        return buffer[line].substr(begin + capture_pos, ByteCount{capture_len});\n    }\n};\n\nusing RegexMatchList = Vector<RegexMatch, MemoryDomain::Regions>;\n\nstruct RegionMatches : UseMemoryDomain<MemoryDomain::Highlight>\n{\n    RegexMatchList begin_matches;\n    RegexMatchList end_matches;\n    RegexMatchList recurse_matches;\n};\n\nstruct ForwardHighlighterApplier\n{\n    DisplayBuffer& display_buffer;\n    HighlightContext& context;\n    DisplayLineList::iterator cur_line = display_buffer.lines().begin();\n    DisplayLineList::iterator end_line = display_buffer.lines().end();\n    DisplayLine::iterator cur_atom = cur_line->begin();\n    DisplayBuffer region_display{};\n\n    void operator()(BufferCoord begin, BufferCoord end, Highlighter& highlighter)\n    {\n        if (begin == end)\n            return;\n\n        auto first_line = std::find_if(cur_line, end_line, [&](auto&& line) { return line.range().end > begin; });\n        if (first_line != cur_line and first_line != end_line)\n            cur_atom = first_line->begin();\n        cur_line = first_line;\n        if (cur_line == end_line or cur_line->range().begin >= end)\n            return;\n\n        auto& region_lines = region_display.lines();\n        region_lines.clear();\n        Vector<std::pair<DisplayLineList::iterator, size_t>> insert_pos;\n        while (cur_line != end_line and cur_line->range().begin < end)\n        {\n            auto& line = *cur_line;\n            auto first = std::find_if(cur_atom, line.end(), [&](auto&& atom) { return atom.has_buffer_range() and atom.end() > begin; });\n            if (first != line.end() and first->type() == DisplayAtom::Range and first->begin() < begin)\n                first = ++line.split(first, begin);\n            auto idx = first - line.begin();\n\n            auto last = std::find_if(first, line.end(), [&](auto&& atom) { return atom.has_buffer_range() and atom.end() > end; });\n            if (last != line.end() and last->type() == DisplayAtom::Range and last->begin() < end)\n                last = ++line.split(last, end);\n\n            if (line.begin() + idx != last)\n            {\n                insert_pos.emplace_back(cur_line, idx);\n                region_lines.push_back(line.extract(line.begin() + idx, last));\n            }\n\n            if (idx != line.atoms().size())\n                break;\n            else if (++cur_line != end_line)\n                cur_atom = cur_line->begin();\n        }\n\n        if (region_lines.empty())\n            return;\n\n        region_display.compute_range();\n        highlighter.highlight(context, region_display, {begin, end});\n\n        for (size_t i = 0; i < insert_pos.size(); ++i)\n        {\n            auto& [line_it, idx] = insert_pos[i];\n            auto& atoms = region_lines[i].atoms();\n            auto it = line_it->insert(\n                line_it->begin() + idx,\n                std::move_iterator(atoms.begin()),\n                std::move_iterator(atoms.end()));\n\n            if (line_it == cur_line)\n                cur_atom = it + atoms.size();\n        }\n    }\n};\n\nconst HighlighterDesc default_region_desc = {\n    \"Parameters: <delegate_type> <delegate_params>...\\n\"\n    \"Define the default region of a regions highlighter\",\n    {}\n};\nconst HighlighterDesc region_desc = {\n    \"Parameters:  [-match-capture] [-recurse <recurse>] <opening> <closing> <type> <params>...\\n\"\n    \"Define a region for a regions highlighter, and apply the given delegate\\n\"\n    \"highlighter as defined by <type> and eventual <params>...\\n\"\n    \"The region starts at <begin> match and ends at the first <end>\",\n    { {\n        { \"match-capture\", { {}, \"only consider region ending/recurse delimiters whose first capture group match the region beginning delimiter\" } },\n        { \"recurse\",       { ArgCompleter{}, \"make the region end on the first ending delimiter that does not close the given parameter\" } } },\n        ParameterDesc::Flags::SwitchesOnlyAtStart | ParameterDesc::Flags::IgnoreUnknownSwitches,\n        3\n    }\n};\nconst HighlighterDesc regions_desc = {\n    \"Holds child region highlighters and segments the buffer in ranges based on those regions\\n\"\n    \"definitions. The regions highlighter finds the next region to start by finding which\\n\"\n    \"of its child region has the leftmost starting point from current position. In between\\n\"\n    \"regions, the default-region child highlighter is applied (if such a child exists)\",\n    {}\n};\nstruct RegionsHighlighter : public Highlighter\n{\npublic:\n    RegionsHighlighter()\n        : Highlighter{HighlightPass::Colorize} {}\n\n    void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override\n    {\n        if (m_regions.empty())\n            return;\n\n        auto display_range = display_buffer.range();\n        const auto& buffer = context.context.buffer();\n        auto& regions = get_regions_for_range(buffer, range);\n\n        auto begin = std::lower_bound(regions.begin(), regions.end(), display_range.begin,\n                                      [](const Region& r, BufferCoord c) { return r.end < c; });\n        auto end = std::lower_bound(begin, regions.end(), display_range.end,\n                                    [](const Region& r, BufferCoord c) { return r.begin < c; });\n        auto correct = [&](BufferCoord c) -> BufferCoord {\n            if (not buffer.is_end(c) and buffer[c.line].length() == c.column)\n                return {c.line+1, 0};\n            return c;\n        };\n\n        auto default_region_it = m_regions.find(m_default_region);\n        const bool apply_default = default_region_it != m_regions.end();\n\n        auto last_begin = (begin == regions.begin()) ? range.begin : (begin-1)->end;\n        kak_assert(begin <= end);\n\n        ForwardHighlighterApplier apply_highlighter{display_buffer, context};\n        for (; begin != end; ++begin)\n        {\n            if (apply_default and last_begin < begin->begin)\n                apply_highlighter(correct(last_begin), correct(begin->begin), *default_region_it->value);\n\n            apply_highlighter(correct(begin->begin), correct(begin->end), *begin->highlighter);\n            last_begin = begin->end;\n        }\n        if (apply_default and last_begin < display_range.end)\n            apply_highlighter(correct(last_begin), range.end, *default_region_it->value);\n\n        display_buffer.compute_range();\n    }\n\n    bool has_children() const override { return true; }\n\n    Highlighter& get_child(StringView path) override\n    {\n        auto sep_it = find(path, '/');\n        StringView id(path.begin(), sep_it);\n        auto it = m_regions.find(id);\n        if (it == m_regions.end())\n            throw child_not_found(format(\"no such id: {}\", id));\n        if (sep_it == path.end())\n            return *it->value;\n        else\n            return it->value->get_child({sep_it+1, path.end()});\n    }\n\n    void add_child(String name, UniquePtr<Highlighter>&& hl, bool override) override\n    {\n        if (not dynamic_cast<RegionHighlighter*>(hl.get()))\n            throw runtime_error{\"only region highlighter can be added as child of a regions highlighter\"};\n        auto it = m_regions.find(name);\n        if (not override and it != m_regions.end())\n            throw runtime_error{format(\"duplicate id: '{}'\", name)};\n\n        UniquePtr<RegionHighlighter> region_hl{dynamic_cast<RegionHighlighter*>(hl.release())};\n        if (region_hl->is_default())\n        {\n            if (not m_default_region.empty())\n                throw runtime_error{\"default region already defined\"};\n            m_default_region = name;\n        }\n\n        if (it != m_regions.end())\n            it->value = std::move(region_hl);\n        else\n            m_regions.insert({std::move(name), std::move(region_hl)});\n        ++m_regions_timestamp;\n    }\n\n    void remove_child(StringView id) override\n    {\n        if (id == m_default_region)\n            m_default_region = String{};\n\n        auto it = m_regions.find(id);\n        if (it == m_regions.end())\n            throw child_not_found(format(\"no such id: {}\", id));\n        m_regions.remove(it);\n        ++m_regions_timestamp;\n    }\n\n    Completions complete_child(StringView path, ByteCount cursor_pos, bool group) const override\n    {\n        auto sep_it = find(path, '/');\n        if (sep_it != path.end())\n        {\n            ByteCount offset = sep_it+1 - path.begin();\n            Highlighter& hl = const_cast<RegionsHighlighter*>(this)->get_child({path.begin(), sep_it});\n            return offset_pos(hl.complete_child(path.substr(offset), cursor_pos - offset, group), offset);\n        }\n\n        auto container = m_regions | transform(&decltype(m_regions)::Item::key);\n        auto completions_flags = group ? Completions::Flags::None : Completions::Flags::Menu;\n        return { 0, 0, complete(path, cursor_pos, container), completions_flags };\n    }\n\n    static UniquePtr<Highlighter> create(HighlighterParameters params, Highlighter*)\n    {\n        if (not params.empty())\n            throw runtime_error{\"unexpected parameters\"};\n        return make_unique_ptr<RegionsHighlighter>();\n    }\n\n    static bool is_regions(Highlighter* parent)\n    {\n        if (dynamic_cast<RegionsHighlighter*>(parent))\n            return true;\n        if (auto* region = dynamic_cast<RegionHighlighter*>(parent))\n            return is_regions(&region->delegate());\n        return false;\n    }\n\n    static UniquePtr<Highlighter> create_region(HighlighterParameters params, Highlighter* parent)\n    {\n        if (not is_regions(parent))\n            throw runtime_error{\"region highlighter can only be added to a regions parent\"};\n\n        ParametersParser parser{params, region_desc.params};\n\n        const bool match_capture = (bool)parser.get_switch(\"match-capture\");\n        if (parser[0].empty() or parser[1].empty())\n            throw runtime_error(\"begin and end must not be empty\");\n\n        const auto& type = parser[2];\n        auto& registry = HighlighterRegistry::instance();\n        auto it = registry.find(type);\n        if (it == registry.end())\n            throw runtime_error(format(\"no such highlighter type: '{}'\", type));\n\n        // validate regexes, TODO: less costly\n        Regex{parser[0]};\n        Regex{parser[1]};\n        if (auto recurse_switch = parser.get_switch(\"recurse\"))\n            Regex{*recurse_switch};\n\n        auto delegate = it->value.factory(parser.positionals_from(3), nullptr);\n        return make_unique_ptr<RegionHighlighter>(std::move(delegate), parser[0], parser[1], parser.get_switch(\"recurse\").value_or(\"\").str(), match_capture);\n    }\n\n    static UniquePtr<Highlighter> create_default_region(HighlighterParameters params, Highlighter* parent)\n    {\n        if (not is_regions(parent))\n            throw runtime_error{\"default-region highlighter can only be added to a regions parent\"};\n\n        static const ParameterDesc param_desc{ {}, ParameterDesc::Flags::SwitchesOnlyAtStart, 1 };\n        ParametersParser parser{params, param_desc};\n\n        const auto& type = parser[0];\n        auto& registry = HighlighterRegistry::instance();\n        auto it = registry.find(type);\n        if (it == registry.end())\n            throw runtime_error(format(\"no such highlighter type: '{}'\", type));\n\n        auto delegate = it->value.factory(parser.positionals_from(1), nullptr);\n        return make_unique_ptr<RegionHighlighter>(std::move(delegate));\n    }\n\nprivate:\n    struct RegionHighlighter : public Highlighter\n    {\n        RegionHighlighter(UniquePtr<Highlighter>&& delegate,\n                          String begin, String end, String recurse,\n                          bool match_capture)\n            : Highlighter{delegate->passes()},\n              m_delegate{std::move(delegate)},\n              m_begin{std::move(begin)}, m_end{std::move(end)}, m_recurse{std::move(recurse)},\n              m_match_capture{match_capture}\n       {\n       }\n\n        RegionHighlighter(UniquePtr<Highlighter>&& delegate)\n            : Highlighter{delegate->passes()}, m_delegate{std::move(delegate)}, m_default{true}\n       {\n       }\n\n        bool has_children() const override\n        {\n            return m_delegate->has_children();\n        }\n\n        Highlighter& get_child(StringView path) override\n        {\n            return m_delegate->get_child(path);\n        }\n\n        void add_child(String name, UniquePtr<Highlighter>&& hl, bool override) override\n        {\n            return m_delegate->add_child(name, std::move(hl), override);\n        }\n\n        void remove_child(StringView id) override\n        {\n            return m_delegate->remove_child(id);\n        }\n\n        Completions complete_child(StringView path, ByteCount cursor_pos, bool group) const override\n        {\n            return m_delegate->complete_child(path, cursor_pos, group);\n        }\n\n        void fill_unique_ids(Vector<StringView>& unique_ids) const override\n        {\n            return m_delegate->fill_unique_ids(unique_ids);\n        }\n\n        void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange range) override\n        {\n            return m_delegate->highlight(context, display_buffer, range);\n        }\n\n\n        bool match_capture() const { return m_match_capture; }\n        bool is_default() const { return m_default; }\n\n        Highlighter& delegate() { return *m_delegate; }\n\n    // private:\n        UniquePtr<Highlighter> m_delegate;\n\n        String m_begin;\n        String m_end;\n        String m_recurse;\n        bool  m_match_capture = false;\n        bool  m_default = false;\n    };\n\n    struct Region\n    {\n        BufferCoord begin;\n        BufferCoord end;\n        RegionHighlighter* highlighter;\n    };\n    using RegionList = Vector<Region, MemoryDomain::Highlight>;\n\n    struct RegexKey\n    {\n        StringView regex;\n        bool match_captures;\n\n        friend size_t hash_value(const RegexKey& key) { return hash_values(key.regex, key.match_captures); }\n        friend bool operator==(const RegexKey&, const RegexKey&) = default;\n    };\n\n    struct Cache\n    {\n        size_t buffer_timestamp = 0;\n        size_t regions_timestamp = 0;\n        LineRangeSet ranges;\n        HashMap<RegexKey, RegexMatchList> matches;\n        HashMap<BufferRange, RegionList, MemoryDomain::Highlight> regions;\n    };\n\n\n    using RegionAndMatch = std::pair<size_t, RegexMatchList::const_iterator>;\n\n    static bool compare_to_begin(const RegexMatch& lhs, BufferCoord rhs)\n    {\n        return lhs.begin_coord() < rhs;\n    }\n\n    RegexMatchList::const_iterator find_matching_end(const Buffer& buffer, BufferCoord beg_pos, const RegexMatchList& end_matches, const RegexMatchList& recurse_matches, Optional<StringView> capture) const\n    {\n        auto end_it = end_matches.begin();\n        auto rec_it = recurse_matches.begin();\n        int recurse_level = 0;\n        while (true)\n        {\n            end_it = std::lower_bound(end_it, end_matches.end(), beg_pos,\n                                      compare_to_begin);\n            rec_it = std::lower_bound(rec_it, recurse_matches.end(), beg_pos,\n                                      compare_to_begin);\n\n            if (end_it == end_matches.end())\n                return end_it;\n\n            while (rec_it != recurse_matches.end() and\n                   rec_it->end_coord() <= end_it->end_coord())\n            {\n                if (not capture or rec_it->capture(buffer) == *capture)\n                    ++recurse_level;\n                ++rec_it;\n            }\n\n            if (not capture or *capture == end_it->capture(buffer))\n            {\n                if (recurse_level == 0)\n                    return end_it;\n                --recurse_level;\n            }\n\n            if (beg_pos != end_it->end_coord())\n                beg_pos = end_it->end_coord();\n            ++end_it;\n        }\n    }\n\n    // find the begin closest to pos in all matches\n    Optional<RegionAndMatch> find_next_begin(const Cache& cache, BufferCoord pos) const\n    {\n        Optional<RegionAndMatch> res;\n\n        for (size_t i = 0; i < m_regions.size(); ++i)\n        {\n            const auto& [key, region] = m_regions.item(i);\n            if (region->is_default())\n                continue;\n\n            const auto& matches = cache.matches.get(RegexKey{region->m_begin, region->match_capture()});\n            auto it = std::lower_bound(matches.begin(), matches.end(), pos, compare_to_begin);\n            if (it != matches.end() and (not res or it->begin_coord() < res->second->begin_coord()))\n                res = RegionAndMatch{i, it};\n        }\n        return res;\n    }\n\n    void add_regex(const String& str, bool captures)\n    {\n        const RegexKey key{str, captures};\n        if (str.empty() or m_regexes.contains(key))\n            return;\n\n        auto flags = RegexCompileFlags::Optimize;\n        if (not captures)\n            flags |= RegexCompileFlags::NoSubs;\n\n        m_regexes.insert({key, Regex{str, flags}});\n    }\n\n    class MatchAdder\n    {\n    public:\n        MatchAdder(RegionsHighlighter& region, const Buffer& buffer, Cache& cache) : m_buffer(buffer)\n        {\n            for (auto& [key, regex] : region.m_regexes)\n                cache.matches[key];\n            for (auto& [key, regex] : region.m_regexes)\n                m_matchers.push_back(Matcher{cache.matches.get(key), regex});\n        }\n\n        ~MatchAdder()\n        {\n            // Move new matches into position.\n            for (auto& [matches, regex, pivot, vm] : m_matchers)\n                std::inplace_merge(matches.begin(), matches.begin() + pivot, matches.end(),\n                                   [](const auto& lhs, const auto& rhs) { return lhs.line < rhs.line; });\n        }\n\n        void add(LineRange range)\n        {\n            for (auto line = range.begin; line < range.end; ++line)\n            {\n                const StringView l = m_buffer[line];\n                const auto flags = RegexExecFlags::NotEndOfLine; // buffer line already ends with \\n\n\n                for (auto& [matches, regex, pivot, vm] : m_matchers)\n                {\n                    auto extra_flags = RegexExecFlags::None;\n                    auto pos = l.begin();\n                    while (vm.exec(pos, l.end(), l.begin(), l.end(), flags | extra_flags))\n                    {\n                        ConstArrayView<const char*> captures = vm.captures();\n                        const bool with_capture = regex.mark_count() > 0 and captures[2] != nullptr and\n                                                  captures[1] - captures[0] < std::numeric_limits<uint16_t>::max();\n                        matches.push_back({\n                            line,\n                            (int)(captures[0] - l.begin()),\n                            (int)(captures[1] - l.begin()),\n                            (uint16_t)(with_capture ? captures[2] - captures[0] : 0),\n                            (uint16_t)(with_capture ? captures[3] - captures[2] : 0)\n                        });\n                        pos = captures[1];\n\n                        extra_flags = (captures[0] == captures[1]) ? RegexExecFlags::NotInitialNull : RegexExecFlags::None;\n                    }\n                }\n            }\n        }\n\n    private:\n        struct Matcher\n        {\n            RegexMatchList& matches;\n            const Regex& regex;\n            size_t pivot = matches.size();\n            ThreadedRegexVM<const char*, RegexMode::Forward | RegexMode::Search> vm{*regex.impl()};\n        };\n\n        const Buffer& m_buffer;\n        Vector<Matcher> m_matchers;\n    };\n\n    void update_changed_lines(const Buffer& buffer, ConstArrayView<LineModification> modifs, Cache& cache)\n    {\n        for (auto& [key, matches] : cache.matches)\n        {\n            // remove out of date matches and update line for others\n            auto ins_pos = matches.begin();\n            for (auto it = ins_pos; it != matches.end(); ++it)\n            {\n                auto modif_it = std::upper_bound(modifs.begin(), modifs.end(), it->line,\n                                                 [](const LineCount& l, const LineModification& c)\n                                                 { return l < c.old_line; });\n\n                if (modif_it != modifs.begin())\n                {\n                    auto& prev = *(modif_it-1);\n                    if (it->line < prev.old_line + prev.num_removed)\n                        continue; // match removed\n\n                    it->line += prev.diff();\n                }\n\n                kak_assert(buffer.is_valid(it->begin_coord()) or\n                           buffer[it->line].length() == it->begin);\n                kak_assert(buffer.is_valid(it->end_coord()) or\n                           buffer[it->line].length() == it->end);\n\n                if (ins_pos != it)\n                    *ins_pos = std::move(*it);\n                ++ins_pos;\n            }\n            matches.erase(ins_pos, matches.end());\n        }\n    }\n\n    bool update_matches(Cache& cache, const Buffer& buffer, LineRange range)\n    {\n        const size_t buffer_timestamp = buffer.timestamp();\n        if (cache.buffer_timestamp == 0 or\n            cache.regions_timestamp != m_regions_timestamp)\n        {\n            m_regexes.clear();\n            cache.matches.clear();\n            for (auto& [key, region] : m_regions)\n            {\n                add_regex(region->m_begin, region->match_capture());\n                add_regex(region->m_end, region->match_capture());\n                add_regex(region->m_recurse, region->match_capture());\n            }\n\n            MatchAdder{*this, buffer, cache}.add(range);\n            cache.ranges.reset(range);\n            cache.buffer_timestamp = buffer_timestamp;\n            cache.regions_timestamp = m_regions_timestamp;\n            return true;\n        }\n        else\n        {\n            bool modified = false;\n            if (cache.buffer_timestamp != buffer_timestamp)\n            {\n                auto modifs = compute_line_modifications(buffer, cache.buffer_timestamp);\n                update_changed_lines(buffer, modifs, cache);\n                cache.ranges.update(modifs);\n                cache.buffer_timestamp = buffer_timestamp;\n                modified = true;\n            }\n\n            MatchAdder matches{*this, buffer, cache};\n            cache.ranges.add_range(range, [&](const LineRange& range) {\n                if (range.begin == range.end)\n                    return;\n                matches.add(range);\n                modified = true;\n            });\n            return modified;\n        }\n    }\n\n    const RegionList& get_regions_for_range(const Buffer& buffer, BufferRange range)\n    {\n        Cache& cache = m_cache.get(buffer);\n        if (update_matches(cache, buffer, {range.begin.line, std::min(buffer.line_count(), range.end.line + 1)}))\n            cache.regions.clear();\n\n        auto it = cache.regions.find(range);\n        if (it != cache.regions.end())\n            return it->value;\n\n        RegionList& regions = cache.regions[range];\n        RegexMatchList empty_matches{};\n\n        for (auto begin = find_next_begin(cache, range.begin); begin; )\n        {\n            auto& [index, beg_it] = *begin;\n            auto& region = *m_regions.item(index).value;\n            auto& end_matches = cache.matches.get(RegexKey{region.m_end, region.match_capture()});\n            auto& recurse_matches = region.m_recurse.empty() ?\n                empty_matches : cache.matches.get(RegexKey{region.m_recurse, region.match_capture()});\n\n            auto end_it = find_matching_end(buffer, beg_it->end_coord(), end_matches, recurse_matches,\n                                            region.match_capture() ? beg_it->capture(buffer) : Optional<StringView>{});\n\n            if (end_it == end_matches.end() or end_it->end_coord() >= range.end) // region continue past range end\n            {\n                auto begin_coord = beg_it->begin_coord();\n                if (begin_coord < range.end)\n                    regions.push_back({begin_coord, range.end, &region});\n                break;\n            }\n\n            auto end_coord = end_it->end_coord();\n            regions.push_back({beg_it->begin_coord(), end_coord, &region});\n\n            // With empty begin and end matches (for example if the regexes\n            // are /\"\\K/ and /(?=\")/), that case can happen, and would\n            // result in an infinite loop.\n            if (end_coord == beg_it->begin_coord())\n            {\n                kak_assert(beg_it->empty() and end_it->empty());\n                ++end_coord.column;\n            }\n            begin = find_next_begin(cache, end_coord);\n        }\n        return regions;\n    }\n\n    HashMap<String, UniquePtr<RegionHighlighter>, MemoryDomain::Highlight> m_regions;\n    HashMap<RegexKey, Regex> m_regexes;\n    String m_default_region;\n\n    size_t m_regions_timestamp = 0;\n    BufferSideCache<Cache> m_cache;\n};\n\nvoid setup_builtin_highlighters(HighlighterGroup& group)\n{\n    group.add_child(\"tabulations\"_str, make_unique_ptr<TabulationHighlighter>());\n    group.add_child(\"unprintable\"_str, make_highlighter(expand_unprintable));\n    group.add_child(\"selections\"_str,  make_highlighter(highlight_selections));\n}\n\nvoid register_highlighters()\n{\n    HighlighterRegistry& registry = HighlighterRegistry::instance();\n\n    registry.insert({\n        \"column\",\n        { create_column_highlighter, &column_desc } });\n    registry.insert({\n        \"default-region\",\n        { RegionsHighlighter::create_default_region, &default_region_desc } });\n    registry.insert({\n        \"dynregex\",\n        { create_dynamic_regex_highlighter, &dynamic_regex_desc } });\n    registry.insert({\n        \"fill\",\n        { create_fill_highlighter, &fill_desc } });\n    registry.insert({\n        \"flag-lines\",\n        { FlagLinesHighlighter::create, &flag_lines_desc } });\n    registry.insert({\n        \"group\",\n        { create_highlighter_group, &higlighter_group_desc } });\n    registry.insert({\n        \"line\",\n        { create_line_highlighter, &line_desc } });\n    registry.insert({\n        \"number-lines\",\n        { LineNumbersHighlighter::create, &line_numbers_desc } });\n    registry.insert({\n        \"ranges\",\n        { RangesHighlighter::create, &ranges_desc } });\n    registry.insert({\n        \"ref\",\n        { ReferenceHighlighter::create, &ref_desc } });\n    registry.insert({\n        \"regex\",\n        { RegexHighlighter::create, &regex_desc } });\n    registry.insert({\n        \"region\",\n        { RegionsHighlighter::create_region, &region_desc } });\n    registry.insert({\n        \"regions\",\n        { RegionsHighlighter::create, &regions_desc } });\n    registry.insert({\n        \"replace-ranges\",\n        { ReplaceRangesHighlighter::create, &replace_ranges_desc } });\n    registry.insert({\n        \"show-matching\",\n        { create_matching_char_highlighter, &show_matching_desc } });\n    registry.insert({\n        \"show-whitespaces\",\n        { ShowWhitespacesHighlighter::create, &show_whitespace_desc } });\n    registry.insert({\n        \"wrap\",\n        { WrapHighlighter::create, &wrap_desc } });\n}\n\n}\n"
  },
  {
    "path": "src/highlighters.hh",
    "content": "#ifndef highlighters_hh_INCLUDED\n#define highlighters_hh_INCLUDED\n\n#include \"color.hh\"\n#include \"highlighter.hh\"\n#include \"option.hh\"\n\nnamespace Kakoune\n{\n\nvoid register_highlighters();\n\nstruct InclusiveBufferRange\n{\n    BufferCoord first, last;\n    friend bool operator==(const InclusiveBufferRange& lhs, const InclusiveBufferRange& rhs) = default;\n};\n\nString option_to_string(InclusiveBufferRange range);\nInclusiveBufferRange option_from_string(Meta::Type<InclusiveBufferRange>, StringView str);\n\nusing LineAndSpec = std::tuple<LineCount, String>;\nusing LineAndSpecList = TimestampedList<LineAndSpec>;\n\nconstexpr StringView option_type_name(Meta::Type<LineAndSpecList>)\n{\n    return \"line-specs\";\n}\nvoid option_update(LineAndSpecList& opt, const Context& context);\nvoid option_list_postprocess(Vector<LineAndSpec, MemoryDomain::Options>& opt);\n\nusing RangeAndString = std::tuple<InclusiveBufferRange, String>;\nusing RangeAndStringList = TimestampedList<RangeAndString>;\n\nconstexpr StringView option_type_name(Meta::Type<RangeAndStringList>)\n{\n    return \"range-specs\";\n}\nvoid option_update(RangeAndStringList& opt, const Context& context);\nvoid option_list_postprocess(Vector<RangeAndString, MemoryDomain::Options>& opt);\nbool option_add_from_strings(Vector<RangeAndString, MemoryDomain::Options>& opt, ConstArrayView<String> strs);\n\n}\n\n#endif // highlighters_hh_INCLUDED\n"
  },
  {
    "path": "src/hook_manager.cc",
    "content": "#include \"hook_manager.hh\"\n\n#include \"debug.hh\"\n#include \"command_manager.hh\"\n#include \"context.hh\"\n#include \"display_buffer.hh\"\n#include \"face_registry.hh\"\n#include \"option_manager.hh\"\n#include \"option_types.hh\"\n#include \"profile.hh\"\n#include \"ranges.hh\"\n#include \"regex.hh\"\n\nnamespace Kakoune\n{\n\nstruct HookManager::HookData\n{\n    String group;\n    HookFlags flags;\n    Regex filter;\n    String commands;\n\n    bool should_run(bool only_always, const Regex& disabled_hooks, StringView param,\n                    MatchResults<const char*>& captures) const\n    {\n        return (not only_always or (flags & HookFlags::Always)) and\n                (group.empty() or disabled_hooks.empty() or\n                 not regex_match(group.begin(), group.end(), disabled_hooks))\n                and regex_match(param.begin(), param.end(), captures, filter);\n    }\n\n    void exec(Hook hook, StringView param, Context& context, const MatchResults<const char*>& captures)\n    {\n        if (context.options()[\"debug\"].get<DebugFlags>() & DebugFlags::Hooks)\n            write_to_debug_buffer(format(\"hook {}({})/{}\",\n                                  enum_desc(Meta::Type<Hook>{})[to_underlying(hook)].name,\n                                  param, group));\n\n        EnvVarMap env_vars{ {\"hook_param\", param.str()} };\n        for (size_t i = 0; i < captures.size(); ++i)\n            env_vars.insert({format(\"hook_param_capture_{}\", i),\n                             {captures[i].first, captures[i].second}});\n        for (auto& c : filter.impl()->named_captures)\n            env_vars.insert({format(\"hook_param_capture_{}\", c.name),\n                             {captures[c.index].first, captures[c.index].second}});\n\n        CommandManager::instance().execute(commands, context, {{}, std::move(env_vars)});\n    }\n\n};\n\nHookManager::HookManager() : m_parent(nullptr) {}\nHookManager::HookManager(HookManager& parent) : SafeCountable{}, m_parent(&parent) {}\nHookManager::~HookManager() = default;\n\nvoid HookManager::add_hook(Hook hook, String group, HookFlags flags, Regex filter, String commands, Context& context)\n{\n    UniquePtr<HookData> hook_data{new HookData{std::move(group), flags, std::move(filter), std::move(commands)}};\n    if (hook == Hook::ModuleLoaded)\n    {\n        const bool only_always = context.hooks_disabled();\n        auto& disabled_hooks = context.options()[\"disabled_hooks\"].get<Regex>();\n\n        for (auto&& name : CommandManager::instance().loaded_modules())\n        {\n            MatchResults<const char*> captures;\n            if (hook_data->should_run(only_always, disabled_hooks, name, captures))\n            {\n                hook_data->exec(hook, name, context, captures);\n                if (hook_data->flags & HookFlags::Once)\n                    return;\n            }\n        }\n    }\n    m_hooks[to_underlying(hook)].push_back(std::move(hook_data));\n}\n\nvoid HookManager::remove_hooks(const Regex& regex)\n{\n    for (auto& list : m_hooks)\n    {\n        list.erase(remove_if(list, [this, &regex](UniquePtr<HookData>& h) {\n                       if (not regex_match(h->group.begin(), h->group.end(), regex))\n                           return false;\n                       m_hooks_trash.push_back(std::move(h));\n                       return true;\n                   }), list.end());\n    }\n}\n\nCandidateList HookManager::complete_hook_group(StringView prefix, ByteCount pos_in_token)\n{\n    CandidateList res;\n    for (auto& list : m_hooks)\n    {\n        auto container = list | transform([](const UniquePtr<HookData>& h) -> const String& { return h->group; });\n        for (auto& c : complete(prefix, pos_in_token, container))\n        {\n            if (!contains(res, c))\n                res.push_back(c);\n        }\n    }\n    return res;\n}\n\nvoid HookManager::run_hook(Hook hook, StringView param, Context& context)\n{\n    const bool only_always = context.hooks_disabled();\n    auto& disabled_hooks = context.options()[\"disabled_hooks\"].get<Regex>();\n\n    struct ToRun { HookData* hook; MatchResults<const char*> captures; };\n    Vector<ToRun> hooks_to_run; // The m_hooks_trash vector ensure hooks wont die during this method\n    for (auto& hook : m_hooks[to_underlying(hook)])\n    {\n        MatchResults<const char*> captures;\n        if (hook->should_run(only_always, disabled_hooks, param, captures))\n            hooks_to_run.push_back({hook.get(), std::move(captures)});\n    }\n\n    if (m_parent)\n        m_parent->run_hook(hook, param, context);\n\n    auto hook_name = enum_desc(Meta::Type<Hook>{})[to_underlying(hook)].name;\n    if (contains(m_running_hooks, std::make_pair(hook, param)))\n    {\n        auto error = format(\"recursive call of hook {}/{}, not executing\", hook_name, param);\n        write_to_debug_buffer(error);\n        return;\n    }\n\n    m_running_hooks.emplace_back(hook, param);\n    auto pop_running_hook = OnScopeEnd([this]{\n        m_running_hooks.pop_back();\n        if (m_running_hooks.empty())\n            m_hooks_trash.clear();\n    });\n\n    ProfileScope profile{context.options()[\"debug\"].get<DebugFlags>(), [&](std::chrono::microseconds duration) {\n        write_to_debug_buffer(format(\"hook '{}({})' took {} us\", hook_name, param, (size_t)duration.count()));\n    }};\n\n    bool hook_error = false;\n    for (auto& to_run : hooks_to_run)\n    {\n        try\n        {\n            to_run.hook->exec(hook, param, context, to_run.captures);\n\n            if (to_run.hook->flags & HookFlags::Once)\n            {\n                auto& hook_list = m_hooks[to_underlying(hook)];\n                if (auto it = find(hook_list, to_run.hook); it != hook_list.end())\n                {\n                    m_hooks_trash.push_back(std::move(*it));\n                    hook_list.erase(it);\n                }\n            }\n        }\n        catch (runtime_error& err)\n        {\n            hook_error = true;\n            write_to_debug_buffer(format(\"error running hook {}({})/{}: {}\",\n                                         hook_name, param, to_run.hook->group, err.what()));\n        }\n    }\n\n    if (hook_error)\n        context.print_status({\n            format(\"Error running hooks for '{}' '{}', see *debug* buffer\",\n                   hook_name, param), context.faces()[\"Error\"] });\n}\n\n}\n"
  },
  {
    "path": "src/hook_manager.hh",
    "content": "#ifndef hook_manager_hh_INCLUDED\n#define hook_manager_hh_INCLUDED\n\n#include \"completion.hh\"\n#include \"safe_ptr.hh\"\n#include \"meta.hh\"\n#include \"enum.hh\"\n#include \"array.hh\"\n#include \"unique_ptr.hh\"\n\nnamespace Kakoune\n{\n\nclass Context;\nclass Regex;\n\nenum class Hook\n{\n    BufCreate,\n    BufNewFile,\n    BufOpenFile,\n    BufClose,\n    BufWritePost,\n    BufReload,\n    BufWritePre,\n    BufOpenFifo,\n    BufCloseFifo,\n    BufReadFifo,\n    BufSetOption,\n    ClientCreate,\n    ClientClose,\n    ClientRenamed,\n    SessionRenamed,\n    InsertChar,\n    InsertDelete,\n    InsertIdle,\n    InsertKey,\n    InsertMove,\n    InsertCompletionHide,\n    InsertCompletionShow,\n    KakBegin,\n    KakEnd,\n    FocusIn,\n    FocusOut,\n    GlobalSetOption,\n    RuntimeError,\n    PromptIdle,\n    NormalIdle,\n    NextKeyIdle,\n    NormalKey,\n    ModeChange,\n    EnterDirectory,\n    RawKey,\n    RegisterModified,\n    WinClose,\n    WinCreate,\n    WinDisplay,\n    WinResize,\n    WinSetOption,\n    ModuleLoaded,\n    User\n};\n\nconstexpr auto enum_desc(Meta::Type<Hook>)\n{\n    return make_array<EnumDesc<Hook>>({\n        {Hook::BufCreate, \"BufCreate\"},\n        {Hook::BufNewFile, \"BufNewFile\"},\n        {Hook::BufOpenFile, \"BufOpenFile\"},\n        {Hook::BufClose, \"BufClose\"},\n        {Hook::BufWritePost, \"BufWritePost\"},\n        {Hook::BufReload, \"BufReload\"},\n        {Hook::BufWritePre, \"BufWritePre\"},\n        {Hook::BufOpenFifo, \"BufOpenFifo\"},\n        {Hook::BufCloseFifo, \"BufCloseFifo\"},\n        {Hook::BufReadFifo, \"BufReadFifo\"},\n        {Hook::BufSetOption, \"BufSetOption\"},\n        {Hook::ClientCreate, \"ClientCreate\"},\n        {Hook::ClientClose, \"ClientClose\"},\n        {Hook::ClientRenamed, \"ClientRenamed\"},\n        {Hook::SessionRenamed, \"SessionRenamed\"},\n        {Hook::InsertChar, \"InsertChar\"},\n        {Hook::InsertDelete, \"InsertDelete\"},\n        {Hook::InsertIdle, \"InsertIdle\"},\n        {Hook::InsertKey, \"InsertKey\"},\n        {Hook::InsertMove, \"InsertMove\"},\n        {Hook::InsertCompletionHide, \"InsertCompletionHide\"},\n        {Hook::InsertCompletionShow, \"InsertCompletionShow\"},\n        {Hook::KakBegin, \"KakBegin\"},\n        {Hook::KakEnd, \"KakEnd\"},\n        {Hook::FocusIn, \"FocusIn\"},\n        {Hook::FocusOut, \"FocusOut\"},\n        {Hook::GlobalSetOption, \"GlobalSetOption\"},\n        {Hook::RuntimeError, \"RuntimeError\"},\n        {Hook::PromptIdle, \"PromptIdle\"},\n        {Hook::NormalIdle, \"NormalIdle\"},\n        {Hook::NextKeyIdle, \"NextKeyIdle\"},\n        {Hook::NormalKey, \"NormalKey\"},\n        {Hook::ModeChange, \"ModeChange\"},\n        {Hook::EnterDirectory, \"EnterDirectory\"},\n        {Hook::RawKey, \"RawKey\"},\n        {Hook::RegisterModified, \"RegisterModified\"},\n        {Hook::WinClose, \"WinClose\"},\n        {Hook::WinCreate, \"WinCreate\"},\n        {Hook::WinDisplay, \"WinDisplay\"},\n        {Hook::WinResize, \"WinResize\"},\n        {Hook::WinSetOption, \"WinSetOption\"},\n        {Hook::ModuleLoaded, \"ModuleLoaded\"},\n        {Hook::User, \"User\"}\n    });\n}\n\nenum class HookFlags\n{\n    None = 0,\n    Always = 1 << 0,\n    Once = 1 << 1\n};\nconstexpr bool with_bit_ops(Meta::Type<HookFlags>) { return true; }\n\nclass HookManager : public SafeCountable\n{\npublic:\n    HookManager(HookManager& parent);\n    ~HookManager();\n\n    void reparent(HookManager& parent) { m_parent = &parent; }\n\n    void add_hook(Hook hook, String group, HookFlags flags,\n                  Regex filter, String commands, Context& context);\n    void remove_hooks(const Regex& regex);\n    CandidateList complete_hook_group(StringView prefix, ByteCount pos_in_token);\n    void run_hook(Hook hook, StringView param, Context& context);\n\nprivate:\n    struct HookData;\n\n    HookManager();\n    // the only one allowed to construct a root hook manager\n    friend class Scope;\n\n    SafePtr<HookManager> m_parent;\n    Array<Vector<UniquePtr<HookData>, MemoryDomain::Hooks>, enum_desc(Meta::Type<Hook>{}).size()> m_hooks;\n\n    mutable Vector<std::pair<Hook, StringView>, MemoryDomain::Hooks> m_running_hooks;\n    mutable Vector<UniquePtr<HookData>, MemoryDomain::Hooks> m_hooks_trash;\n};\n\n}\n\n#endif // hook_manager_hh_INCLUDED\n"
  },
  {
    "path": "src/input_handler.cc",
    "content": "#include \"input_handler.hh\"\n\n#include \"buffer.hh\"\n#include \"debug.hh\"\n#include \"command_manager.hh\"\n#include \"client.hh\"\n#include \"event_manager.hh\"\n#include \"hook_manager.hh\"\n#include \"face_registry.hh\"\n#include \"keymap_manager.hh\"\n#include \"option_manager.hh\"\n#include \"insert_completer.hh\"\n#include \"normal.hh\"\n#include \"option_types.hh\"\n#include \"regex.hh\"\n#include \"register_manager.hh\"\n#include \"user_interface.hh\"\n#include \"window.hh\"\n#include \"word_db.hh\"\n\n#include <concepts>\n#include <utility>\n#include <limits>\n\nnamespace Kakoune\n{\n\nclass InputMode : public RefCountable\n{\npublic:\n    InputMode(InputHandler& input_handler) : m_input_handler(input_handler) {}\n    ~InputMode() override = default;\n    InputMode(const InputMode&) = delete;\n    InputMode& operator=(const InputMode&) = delete;\n\n    void handle_key(Key key) { RefPtr<InputMode> keep_alive{this}; on_key(key); }\n    virtual void paste(StringView content);\n    virtual void on_raw_key() {}\n\n    virtual void on_enabled(bool from_pop) {}\n    virtual void on_disabled(bool from_push) {}\n\n    virtual void refresh_ifn() {}\n\n    bool enabled() const { return &m_input_handler.current_mode() == this; }\n    Context& context() const { return m_input_handler.context(); }\n\n    virtual ModeInfo mode_info() const = 0;\n\n    virtual KeymapMode keymap_mode() const = 0;\n\n    virtual StringView name() const = 0;\n\n    using Insertion = InputHandler::Insertion;\n\nprotected:\n    virtual void on_key(Key key) = 0;\n\n    void push_mode(InputMode* new_mode) { m_input_handler.push_mode(new_mode); }\n    void pop_mode() { m_input_handler.pop_mode(this); }\n\n    void record_key(Key key) { m_input_handler.record_key(key); }\n    void drop_last_recorded_key() { m_input_handler.drop_last_recorded_key(); }\n\nprivate:\n    InputHandler& m_input_handler;\n};\n\nvoid InputMode::paste(StringView content)\n{\n    try\n    {\n        Buffer& buffer = context().buffer();\n        const bool linewise = not content.empty() and content.back() == '\\n';\n        ScopedEdition edition{context()};\n        ScopedSelectionEdition selection_edition{context()};\n        context().selections().for_each([&buffer, content=std::move(content), linewise]\n                                        (size_t index, Selection& sel) {\n            auto& min = sel.min();\n            auto& max = sel.max();\n            BufferRange range =\n                buffer.insert(paste_pos(buffer, min, max, PasteMode::Insert, linewise), content);\n            min = range.begin;\n            max = range.end > range.begin ? buffer.char_prev(range.end) : range.begin;\n        }, false);\n    }\n    catch (Kakoune::runtime_error& error)\n    {\n        write_to_debug_buffer(format(\"Error: {}\", error.what()));\n        context().print_status({error.what().str(), context().faces()[\"Error\"] });\n        context().hooks().run_hook(Hook::RuntimeError, error.what(), context());\n    }\n}\n\nnamespace InputModes\n{\n\nstd::chrono::milliseconds get_idle_timeout(const Context& context)\n{\n    return std::chrono::milliseconds{context.options()[\"idle_timeout\"].get<int>()};\n}\n\nstd::chrono::milliseconds get_fs_check_timeout(const Context& context)\n{\n    return std::chrono::milliseconds{context.options()[\"fs_check_timeout\"].get<int>()};\n}\n\nstruct MouseHandler\n{\n    bool handle_key(Key key, Context& context)\n    {\n        if (not context.has_window())\n            return false;\n\n        Buffer& buffer = context.buffer();\n        // bits above these potentially store additional information\n        constexpr auto mask = (Key::Modifiers) 0x7FF;\n        constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift | Key::Modifiers::MouseButtonMask | ~mask;\n\n        switch ((key.modifiers & ~modifiers).value)\n        {\n        case Key::Modifiers::MousePress:\n            switch (key.mouse_button())\n            {\n            case Key::MouseButton::Right: {\n                m_dragging.reset();\n                auto cursor = context.window().buffer_coord(key.coord());\n                if (not cursor)\n                {\n                    context.ensure_cursor_visible = false;\n                    return true;\n                }\n                ScopedSelectionEdition selection_edition{context};\n                auto& selections = context.selections();\n                if (key.modifiers & Key::Modifiers::Control)\n                    selections = {{selections.begin()->anchor(), *cursor}};\n                else\n                    selections.main() = {selections.main().anchor(), *cursor};\n                selections.sort_and_merge_overlapping();\n                return true;\n            }\n\n            case Key::MouseButton::Left: {\n                m_dragging.reset(new ScopedSelectionEdition{context});\n                auto anchor = context.window().buffer_coord(key.coord());\n                if (not anchor)\n                {\n                    context.ensure_cursor_visible = false;\n                    return true;\n                }\n                m_anchor = *anchor;\n                if (not (key.modifiers & Key::Modifiers::Control))\n                    context.selections_write_only() = { buffer, m_anchor};\n                else\n                {\n                    auto& selections = context.selections();\n                    size_t main = selections.size();\n                    selections.push_back({m_anchor});\n                    selections.set_main_index(main);\n                    selections.sort_and_merge_overlapping();\n                }\n                return true;\n            }\n\n            default: return true;\n            }\n\n        case Key::Modifiers::MouseRelease: {\n            auto cursor = context.window().buffer_coord(key.coord());\n            if (not m_dragging or not cursor)\n            {\n                context.ensure_cursor_visible = false;\n                return true;\n            }\n            auto& selections = context.selections();\n            selections.main() = {buffer.clamp(m_anchor), *cursor};\n            selections.sort_and_merge_overlapping();\n            m_dragging.reset();\n            return true;\n        }\n\n        case Key::Modifiers::MousePos: {\n            auto cursor = context.window().buffer_coord(key.coord());\n            if (not m_dragging or not cursor)\n            {\n                context.ensure_cursor_visible = false;\n                return true;\n            }\n            auto& selections = context.selections();\n            selections.main() = {buffer.clamp(m_anchor), *cursor};\n            selections.sort_and_merge_overlapping();\n            return true;\n        }\n\n        case Key::Modifiers::Scroll:\n            scroll_window(context, key.scroll_amount(), m_dragging ? OnHiddenCursor::MoveCursor : OnHiddenCursor::PreserveSelections);\n            return true;\n\n        default: return false;\n        }\n    }\n\nprivate:\n    UniquePtr<ScopedSelectionEdition> m_dragging;\n    BufferCoord m_anchor;\n};\n\nconstexpr StringView register_doc =\n    \"Special registers:\\n\"\n    \"[0-9]: selections capture group\\n\"\n    \"%:     buffer name\\n\"\n    \".:     selection contents\\n\"\n    \"#:     selection index\\n\"\n    \"_:     null register\\n\"\n    \"\\\":     default yank/paste register\\n\"\n    \"@:     default macro register\\n\"\n    \"/:     default search register\\n\"\n    \"^:     default mark register\\n\"\n    \"|:     default shell command register\\n\"\n    \"::     last entered command\\n\";\n\nclass Normal : public InputMode\n{\npublic:\n    Normal(InputHandler& input_handler, bool single_command = false)\n        : InputMode(input_handler),\n          m_idle_timer{TimePoint::max(),\n                       context().flags() & Context::Flags::Draft ?\n                           Timer::Callback{} : [this](Timer&) {\n              RefPtr<InputMode> keep_alive{this}; // hook could trigger pop_mode()\n              if (context().has_client())\n                  context().client().clear_pending();\n\n              context().hooks().run_hook(Hook::NormalIdle, \"\", context());\n          }},\n          m_fs_check_timer{TimePoint::max(),\n                           context().flags() & Context::Flags::Draft ?\n                            Timer::Callback{} : Timer::Callback{[this](Timer& timer) {\n              if (context().has_client())\n                  context().client().check_if_buffer_needs_reloading();\n              timer.set_next_date(Clock::now() + get_fs_check_timeout(context()));\n          }}},\n          m_state(single_command ? State::SingleCommand : State::Normal)\n    {}\n\n    void on_enabled(bool from_pop) override\n    {\n        if (m_state == State::PopOnEnabled)\n            return pop_mode();\n\n        if (not (context().flags() & Context::Flags::Draft))\n        {\n            if (context().has_client())\n                context().client().check_if_buffer_needs_reloading();\n\n            m_fs_check_timer.set_next_date(Clock::now() + get_fs_check_timeout(context()));\n            m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n        }\n\n        if (m_hooks_disabled and not m_in_on_key)\n        {\n            context().hooks_disabled().unset();\n            m_hooks_disabled = false;\n        }\n    }\n\n    void on_disabled(bool from_push) override\n    {\n        m_idle_timer.disable();\n        m_fs_check_timer.disable();\n\n        if (not from_push and m_hooks_disabled)\n        {\n            context().hooks_disabled().unset();\n            m_hooks_disabled = false;\n        }\n    }\n\n    void on_key(Key key) override\n    {\n        bool should_clear = false;\n\n        kak_assert(m_state != State::PopOnEnabled);\n        ScopedSetBool set_in_on_key{m_in_on_key};\n\n        bool do_restore_hooks = false;\n        auto restore_hooks = OnScopeEnd([&, this]{\n            if (m_hooks_disabled and enabled() and do_restore_hooks)\n            {\n                context().hooks_disabled().unset();\n                m_hooks_disabled = false;\n            }\n        });\n\n        const bool transient = context().flags() & Context::Flags::Draft;\n\n        if (m_mouse_handler.handle_key(key, context()))\n        {\n            should_clear = true;\n\n            if (not transient)\n                m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n        }\n        else if (auto cp = key.codepoint(); cp and isdigit(*cp))\n        {\n            long long new_val = (long long)m_params.count * 10 + *cp - '0';\n            if (new_val > std::numeric_limits<int>::max())\n                context().print_status({ \"parameter overflowed\", context().faces()[\"Error\"] });\n            else\n                m_params.count = new_val;\n        }\n        else if (key == Key::Backspace)\n            m_params.count /= 10;\n        else if (key == '\\\\')\n        {\n            if (not m_hooks_disabled)\n            {\n                m_hooks_disabled = true;\n                context().hooks_disabled().set();\n            }\n        }\n        else if (key == '\"')\n        {\n            on_next_key_with_autoinfo(context(), \"register\", KeymapMode::None,\n                [this](Key key, Context& context) {\n                    auto cp = key.codepoint();\n                    if (not cp or key == Key::Escape)\n                        return;\n                    if (*cp <= 127)\n                        m_params.reg = *cp;\n                    else\n                        context.print_status(\n                            { format(\"invalid register '{}'\", *cp),\n                              context.faces()[\"Error\"] });\n                }, \"enter target register\", register_doc.str());\n        }\n        else\n        {\n            auto pop_if_single_command = OnScopeEnd([this] {\n                if (m_state == State::SingleCommand and enabled())\n                     pop_mode();\n                else if (m_state == State::SingleCommand)\n                     m_state = State::PopOnEnabled;\n            });\n\n            should_clear = true;\n\n            // Hack to parse keys sent by terminals using the 8th bit to mark the\n            // meta key. In normal mode, give priority to a potential alt-key than\n            // the accentuated character.\n            if (key.key >= 127 and key.key < 256)\n            {\n                key.modifiers |= Key::Modifiers::Alt;\n                key.key &= 0x7f;\n            }\n\n            do_restore_hooks = true;\n            if (auto command = get_normal_command(key))\n            {\n                auto autoinfo = context().options()[\"autoinfo\"].get<AutoInfo>();\n                if (autoinfo & AutoInfo::Normal and context().has_client())\n                    context().client().info_show(to_string(key), command->docstring.str(),\n                                                 {}, InfoStyle::Prompt);\n\n                // reset m_params now to be reentrant\n                NormalParams params = m_params;\n                m_params = { 0, 0 };\n\n                command->func(context(), params);\n            }\n            else\n                m_params = { 0, 0 };\n        }\n\n        context().hooks().run_hook(Hook::NormalKey, to_string(key), context());\n        if (should_clear and not transient and context().has_client())\n            context().client().schedule_clear();\n        if (enabled() and not transient) // The hook might have changed mode\n            m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n    }\n\n    ModeInfo mode_info() const override\n    {\n        auto info_face = context().faces()[\"StatusLineInfo\"];\n        auto value_face = context().faces()[\"StatusLineValue\"];\n\n        auto& sels = context().selections();\n        int hidden_count = 0;\n        for (auto& sel : sels)\n            hidden_count += context().has_window() and not context().window().display_coord(sel.cursor());\n\n        AtomList atoms;\n        if (sels.size() == 1)\n        {\n            atoms.emplace_back(format(\"{} sel\", sels.size()), info_face);\n            if (hidden_count)\n                atoms.emplace_back(\" (hidden)\", info_face);\n        }\n        else\n        {\n            atoms.emplace_back(format(\"{} sels ({})\", sels.size(), sels.main_index() + 1), info_face);\n            if (hidden_count)\n                atoms.emplace_back(format(\" ({} hidden)\", hidden_count), info_face);\n        }\n\n        if (m_params.count != 0)\n        {\n            atoms.emplace_back(\" param=\", info_face);\n            atoms.emplace_back(to_string(m_params.count), value_face);\n        }\n        if (m_params.reg)\n        {\n            atoms.emplace_back(\" reg=\", info_face);\n            atoms.emplace_back(StringView(m_params.reg).str(), value_face);\n        }\n        return {atoms, m_params};\n    }\n\n    void paste(StringView content) override\n    {\n        InputMode::paste(content);\n        if (not (context().flags() & Context::Flags::Draft))\n        {\n            if (context().has_client())\n                context().client().schedule_clear();\n            m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n        }\n    }\n\n    KeymapMode keymap_mode() const override { return KeymapMode::Normal; }\n\n    StringView name() const override { return \"normal\"; }\n\nprivate:\n    friend struct InputHandler::ScopedForceNormal;\n\n    NormalParams m_params = { 0, 0 };\n    bool m_hooks_disabled = false;\n    NestedBool m_in_on_key;\n    Timer m_idle_timer;\n    Timer m_fs_check_timer;\n    MouseHandler m_mouse_handler;\n\n    enum class State { Normal, SingleCommand, PopOnEnabled };\n    State m_state;\n};\n\ntemplate<WordType word_type>\nvoid to_next_word_begin(CharCount& pos, StringView line)\n{\n    const CharCount len = line.char_length();\n    if (pos == len)\n        return;\n    if (word_type == Word and is_punctuation(line[pos]))\n    {\n        while (pos != len and is_punctuation(line[pos]))\n            ++pos;\n    }\n    else if (is_word<word_type>(line[pos]))\n    {\n        while (pos != len and is_word<word_type>(line[pos]))\n            ++pos;\n    }\n    while (pos != len and is_horizontal_blank(line[pos]))\n        ++pos;\n}\n\ntemplate<WordType word_type>\nvoid to_next_word_end(CharCount& pos, StringView line)\n{\n    const CharCount len = line.char_length();\n    if (pos + 1 >= len)\n        return;\n    ++pos;\n\n    while (pos != len and is_horizontal_blank(line[pos]))\n        ++pos;\n\n    if (word_type == Word and is_punctuation(line[pos]))\n    {\n        while (pos != len and is_punctuation(line[pos]))\n            ++pos;\n    }\n    else if (is_word<word_type>(line[pos]))\n    {\n        while (pos != len and is_word<word_type>(line[pos]))\n            ++pos;\n    }\n    --pos;\n}\n\ntemplate<WordType word_type>\nvoid to_prev_word_begin(CharCount& pos, StringView line)\n{\n    if (pos == 0_char)\n        return;\n    --pos;\n\n    while (pos != 0_char and is_horizontal_blank(line[pos]))\n        --pos;\n\n    if (word_type == Word and is_punctuation(line[pos]))\n    {\n        while (pos != 0_char and is_punctuation(line[pos]))\n            --pos;\n        if (!is_punctuation(line[pos]))\n            ++pos;\n    }\n    else if (is_word<word_type>(line[pos]))\n    {\n        while (pos != 0_char and is_word<word_type>(line[pos]))\n            --pos;\n        if (!is_word<word_type>(line[pos]))\n            ++pos;\n     }\n}\n\nclass LineEditor\n{\npublic:\n    LineEditor(const FaceRegistry& faces) : m_faces{faces} {}\n\n    void handle_key(Key key)\n    {\n        auto erase_move = [this](auto&& move_func) {\n            auto old_pos = m_cursor_pos;\n            move_func(m_cursor_pos, m_line);\n            if (m_cursor_pos > old_pos)\n                std::swap(m_cursor_pos, old_pos);\n            m_clipboard = m_line.substr(m_cursor_pos, old_pos - m_cursor_pos).str();\n            m_line = m_line.substr(0, m_cursor_pos) + m_line.substr(old_pos);\n        };\n\n        if (key == Key::Left or key == ctrl('b'))\n        {\n            if (m_cursor_pos > 0)\n                --m_cursor_pos;\n        }\n        else if (key == Key::Right or key == ctrl('f'))\n        {\n            if (m_cursor_pos < m_line.char_length())\n                ++m_cursor_pos;\n        }\n        else if (key == Key::Home or key == ctrl('a'))\n            m_cursor_pos = 0;\n        else if (key == Key::End or key == ctrl('e'))\n            m_cursor_pos = m_line.char_length();\n        else if (key == Key::Backspace or key == shift(Key::Backspace) or key == ctrl('h'))\n        {\n            if (m_cursor_pos != 0)\n            {\n                m_line = m_line.substr(0_char, m_cursor_pos - 1)\n                       + m_line.substr(m_cursor_pos);\n\n                --m_cursor_pos;\n            }\n        }\n        else if (key == Key::Delete or key == ctrl('d'))\n        {\n            if (m_cursor_pos != m_line.char_length())\n                m_line = m_line.substr(0, m_cursor_pos)\n                       + m_line.substr(m_cursor_pos+1);\n        }\n        else if (key == alt('f'))\n            to_next_word_begin<Word>(m_cursor_pos, m_line);\n        else if (key == alt('F'))\n            to_next_word_begin<WORD>(m_cursor_pos, m_line);\n        else if (key == alt('b'))\n            to_prev_word_begin<Word>(m_cursor_pos, m_line);\n        else if (key == alt('B'))\n            to_prev_word_begin<WORD>(m_cursor_pos, m_line);\n        else if (key == alt('e'))\n            to_next_word_end<Word>(m_cursor_pos, m_line);\n        else if (key == alt('E'))\n            to_next_word_end<WORD>(m_cursor_pos, m_line);\n        else if (key == ctrl('k'))\n        {\n            m_clipboard = m_line.substr(m_cursor_pos).str();\n            m_line = m_line.substr(0, m_cursor_pos).str();\n        }\n        else if (key == ctrl('u'))\n        {\n            m_clipboard = m_line.substr(0, m_cursor_pos).str();\n            m_line = m_line.substr(m_cursor_pos).str();\n            m_cursor_pos = 0;\n        }\n        else if (key == ctrl('w') or key == alt(Key::Backspace))\n            erase_move(&to_prev_word_begin<Word>);\n        else if (key == ctrl('W'))\n            erase_move(&to_prev_word_begin<WORD>);\n        else if (key == alt('d'))\n            erase_move(&to_next_word_begin<Word>);\n        else if (key == alt('D'))\n            erase_move(&to_next_word_begin<WORD>);\n        else if (key == ctrl('y'))\n        {\n            m_line = m_line.substr(0, m_cursor_pos) + m_clipboard + m_line.substr(m_cursor_pos);\n            m_cursor_pos += m_clipboard.char_length();\n        }\n        else if (auto cp = key.codepoint())\n            insert(*cp);\n    }\n\n    void insert(Codepoint cp)\n    {\n        m_line = m_line.substr(0, m_cursor_pos) + String{cp}\n               + m_line.substr(m_cursor_pos);\n        ++m_cursor_pos;\n    }\n\n    void insert(StringView str)\n    {\n        insert_from(m_cursor_pos, str);\n    }\n\n    void insert_from(CharCount start, StringView str)\n    {\n        kak_assert(start <= m_cursor_pos);\n        m_line = m_line.substr(0, start) + str\n               + m_line.substr(m_cursor_pos);\n       m_cursor_pos = start + str.char_length();\n    }\n\n    void reset(String line, StringView empty_text)\n    {\n        m_line = std::move(line);\n        m_empty_text = empty_text;\n        m_cursor_pos = m_line.char_length();\n    }\n\n    const String& line() const { return m_line; }\n    CharCount cursor_pos() const { return m_cursor_pos; }\n\n    ColumnCount cursor_display_column() const\n    {\n        return m_line.substr(0, m_cursor_pos).column_length();\n    }\n\n    DisplayLine build_display_line(ColumnCount in_width)\n    {\n        const bool empty = m_line.empty();\n        StringView str = empty ? m_empty_text : m_line;\n\n        const Face line_face = m_faces[empty ? \"StatusLineInfo\" : \"StatusLine\"];\n        const Face cursor_face = m_faces[\"StatusCursor\"];\n\n        if (m_cursor_pos == str.char_length())\n            return DisplayLine{{ { str.str(), line_face }, { \" \"_str, cursor_face} } };\n        else\n            return DisplayLine({ { str.substr(0, m_cursor_pos).str(), line_face },\n                                 { str.substr(m_cursor_pos,1).str(), cursor_face },\n                                 { str.substr(m_cursor_pos+1).str(), line_face } });\n    }\nprivate:\n    CharCount  m_cursor_pos = 0;\n\n    String     m_line;\n    StringView m_empty_text = {};\n    String     m_clipboard;\n\n    const FaceRegistry& m_faces;\n};\n\nstatic Optional<Codepoint> get_raw_codepoint(Key key)\n{\n    if (auto cp = key.codepoint())\n        return cp;\n    else if (key.modifiers == Key::Modifiers::Control and\n             ((key.key >= '@' and key.key <= '_') or\n              (key.key >= 'a' and key.key <= 'z')))\n        return {(Codepoint)(to_upper((char)key.key) - '@')};\n    return {};\n}\n\nclass Prompt : public InputMode\n{\npublic:\n    Prompt(InputHandler& input_handler, StringView prompt,\n           String initstr, String emptystr, Face face, PromptFlags flags,\n           char history_register, PromptCompleter completer, PromptCallback callback)\n        : InputMode(input_handler), m_callback(std::move(callback)), m_completer(std::move(completer)),\n          m_prompt(prompt.str()), m_prompt_face(face),\n          m_empty_text{std::move(emptystr)},\n          // This prompt may outlive local scopes so ignore local faces.\n          m_line_editor{context().faces(false)}, m_flags(flags),\n          m_history{RegisterManager::instance()[history_register]},\n          m_current_history{-1},\n          m_auto_complete{context().options()[\"autocomplete\"].get<AutoComplete>() & AutoComplete::Prompt},\n          m_idle_timer{TimePoint::max(), context().flags() & Context::Flags::Draft ?\n                           Timer::Callback{} : [this](Timer&) {\n                           RefPtr<InputMode> keep_alive{this}; // hook or m_callback could trigger pop_mode()\n                           if (m_auto_complete and m_refresh_completion_pending)\n                               refresh_completions();\n                           if (m_line_changed)\n                           {\n                               m_callback(m_line_editor.line(), PromptEvent::Change, context());\n                               m_line_changed = false;\n                           }\n                           context().hooks().run_hook(Hook::PromptIdle, \"\", context());\n                       }}\n    {\n        m_line_editor.reset(std::move(initstr), m_empty_text);\n    }\n\n    void on_key(Key key) override\n    {\n        const String& line = m_line_editor.line();\n\n        auto can_auto_insert_completion = [&] {\n            const bool has_completions = not m_completions.candidates.empty();\n            const bool completion_selected = m_current_completion != -1;\n            const bool text_entered = m_completions.start != line.byte_count_to(m_line_editor.cursor_pos());\n            const bool at_end = line.byte_count_to(m_line_editor.cursor_pos()) == line.length();\n            return (m_completions.flags & Completions::Flags::Menu) and\n                has_completions and\n                not completion_selected and at_end and\n                (not (m_completions.flags & Completions::Flags::NoEmpty) or text_entered);\n        };\n\n        if (key == Key::Return)\n        {\n            if (can_auto_insert_completion())\n            {\n                const String& completion = m_completions.candidates.front();\n                m_line_editor.insert_from(line.char_count_to(m_completions.start),\n                                          completion);\n            }\n\n            history_push(line);\n            context().print_status(DisplayLine{});\n            if (context().has_client())\n                context().client().menu_hide();\n\n            // Maintain hooks disabled in callback if they were before pop_mode\n            ScopedSetBool disable_hooks(context().hooks_disabled(),\n                                        context().hooks_disabled());\n            pop_mode();\n            // call callback after pop_mode so that callback\n            // may change the mode\n            m_callback(line, PromptEvent::Validate, context());\n            return;\n        }\n        else if (key == Key::Escape or key == ctrl('c') or\n                 ((key == Key::Backspace or key == shift(Key::Backspace) or key == ctrl('h')) and line.empty()))\n        {\n            history_push(line);\n            context().print_status(DisplayLine{});\n            if (context().has_client())\n                context().client().menu_hide();\n\n            // Maintain hooks disabled in callback if they were before pop_mode\n            ScopedSetBool disable_hooks(context().hooks_disabled(),\n                                        context().hooks_disabled());\n            pop_mode();\n            m_callback(line, PromptEvent::Abort, context());\n            return;\n        }\n        else if (key == ctrl('r'))\n        {\n            on_next_key_with_autoinfo(context(), \"register\", KeymapMode::None,\n                [this](Key key, Context&) {\n                    const bool joined = (bool)(key.modifiers & Key::Modifiers::Alt);\n                    const bool quoted = (bool)(key.modifiers & Key::Modifiers::Control);\n                    key.modifiers &= ~(Key::Modifiers::Alt | Key::Modifiers::Control);\n\n                    auto cp = key.codepoint();\n                    if (not cp or key == Key::Escape)\n                        return;\n\n                    auto* quoter = Kakoune::quoter(quoted ? Quoting::Kakoune : Quoting::Raw);\n                    m_line_editor.insert(\n                        joined ? join(RegisterManager::instance()[*cp].get(context()) | transform(quoter), ' ', false)\n                               : quoter(context().main_sel_register_value(String{*cp})));\n\n                    display();\n                    m_line_changed = true;\n                    m_refresh_completion_pending = true;\n                }, \"enter register name\", register_doc.str());\n            display();\n            return;\n        }\n        else if (key == ctrl('v'))\n        {\n            on_next_key_with_autoinfo(context(), \"raw-key\", KeymapMode::None,\n                [this](Key key, Context&) {\n                    if (auto cp = get_raw_codepoint(key))\n                    {\n                        m_line_editor.insert(*cp);\n                        display();\n                        m_line_changed = true;\n                        m_refresh_completion_pending = true;\n                    }\n                }, \"raw insert\", \"enter key to insert\");\n            display();\n            return;\n        }\n        else if (key == Key::Up or key == ctrl('p'))\n        {\n            auto history = m_history.get(context());\n            m_current_history = std::min(static_cast<int>(history.size()) - 1, m_current_history);\n            if (m_current_history == -1)\n               m_prefix = line;\n            auto next = find_if(history.subrange(m_current_history + 1), [this](StringView s) { return prefix_match(s, m_prefix); });\n            if (next != history.end())\n            {\n                m_current_history = next - history.begin();\n                m_line_editor.reset(*next, m_empty_text);\n            }\n            clear_completions();\n            m_refresh_completion_pending = true;\n        }\n        else if (key == Key::Down or key == ctrl('n')) // next\n        {\n            auto history = m_history.get(context());\n            m_current_history = std::min(static_cast<int>(history.size()) - 1, m_current_history);\n            if (m_current_history >= 0)\n            {\n                auto next = find_if(history.subrange(0, m_current_history) | reverse(), [this](StringView s) { return prefix_match(s, m_prefix); });\n                m_current_history = history.rend() - next - 1;\n                m_line_editor.reset(next != history.rend() ? *next : m_prefix, m_empty_text);\n                clear_completions();\n                m_refresh_completion_pending = true;\n            }\n        }\n        else if (key == Key::Tab or key == shift(Key::Tab) or key.modifiers == Key::Modifiers::MenuSelect) // completion\n        {\n            CandidateList& candidates = m_completions.candidates;\n\n            if (m_auto_complete and m_refresh_completion_pending)\n                refresh_completions();\n            if (candidates.empty()) // manual completion, we need to ask our completer for completions\n            {\n                refresh_completions();\n                if ((not m_prefix_in_completions and candidates.size() > 1) or\n                    candidates.size() > 2)\n                    return;\n            }\n\n            if (candidates.empty())\n                return;\n\n            const bool reverse = (key == shift(Key::Tab));\n            if (key.modifiers == Key::Modifiers::MenuSelect)\n                m_current_completion = clamp<int>(key.key, 0, candidates.size() - 1);\n            else if (not reverse and ++m_current_completion >= candidates.size())\n                m_current_completion = 0;\n            else if (reverse and --m_current_completion < 0)\n                m_current_completion = candidates.size()-1;\n\n            const String& completion = candidates[m_current_completion];\n            if (context().has_client())\n                context().client().menu_select(m_current_completion);\n\n            m_line_editor.insert_from(line.char_count_to(m_completions.start),\n                                      completion);\n\n            // when we have only one completion candidate, make next tab complete\n            // from the new content.\n            if (candidates.size() == 1 or\n                (m_prefix_in_completions and candidates.size() == 2))\n            {\n                m_current_completion = -1;\n                candidates.clear();\n                m_refresh_completion_pending = true;\n            }\n        }\n        else if (key == ctrl('x'))\n        {\n            on_next_key_with_autoinfo(context(), \"explicit-completion\", KeymapMode::None,\n                [this](Key key, Context&) {\n                    m_explicit_completer = ArgCompleter{};\n\n                    if (key.key == 'f')\n                        use_explicit_completer([](const Context& context, StringView token) {\n                            return complete_filename(token, context.options()[\"ignored_files\"].get<Regex>(),\n                                                     token.length(), FilenameFlags::Expand);\n                        });\n                    else if (key.key == 'w')\n                        use_explicit_completer([](const Context& context, StringView token) {\n                            CandidateList candidates;\n                            for_n_best(get_word_db(context.buffer()).find_matching(token),\n                                       100, [](auto& lhs, auto& rhs){ return rhs < lhs; },\n                                       [&](RankedMatch& m) {\n                                candidates.push_back(m.candidate().str());\n                                return true;\n                            });\n                            return candidates;\n                        });\n\n                    if (m_explicit_completer)\n                        refresh_completions();\n                }, \"enter completion type\",\n                \"f: filename\\n\"\n                \"w: buffer word\\n\");\n        }\n        else if (key == ctrl('o'))\n        {\n            m_explicit_completer = ArgCompleter{};\n            m_auto_complete = not m_auto_complete;\n\n            if (m_auto_complete)\n                refresh_completions();\n            else if (context().has_client())\n            {\n                clear_completions();\n                context().client().menu_hide();\n            }\n        }\n        else if (key == alt('!'))\n        {\n            try\n            {\n                m_line_editor.reset(expand(line, context()), m_empty_text);\n            }\n            catch (std::runtime_error& error)\n            {\n                context().print_status({error.what(), context().faces()[\"Error\"]});\n                return;\n            }\n        }\n        else if (key == alt(';'))\n        {\n            push_mode(new Normal(context().input_handler(), true));\n            return;\n        }\n        else\n        {\n            if ((key == Key::Space or key == shift(Key::Space)) and\n                not (m_completions.flags & Completions::Flags::Quoted) and // if token is quoted, this space does not end it\n                can_auto_insert_completion())\n                m_line_editor.insert_from(line.char_count_to(m_completions.start),\n                                          m_completions.candidates.front());\n\n            m_line_editor.handle_key(key);\n            clear_completions();\n            m_refresh_completion_pending = true;\n        }\n\n        display();\n        m_line_changed = true;\n        if (enabled() and not (context().flags() & Context::Flags::Draft)) // The callback might have disabled us\n            m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n    }\n\n    void refresh_ifn() override\n    {\n        bool explicit_completion_selected = m_current_completion != -1 and\n            (not m_prefix_in_completions or m_current_completion != m_completions.candidates.size() - 1);\n        if (not enabled() or (context().flags() & Context::Flags::Draft) or explicit_completion_selected)\n            return;\n\n        if (auto next_date = Clock::now() + get_idle_timeout(context());\n            next_date < m_idle_timer.next_date())\n            m_idle_timer.set_next_date(next_date);\n        m_refresh_completion_pending = true;\n    }\n\n    void paste(StringView content) override\n    {\n        m_line_editor.insert(content);\n        clear_completions();\n        m_refresh_completion_pending = true;\n        display();\n        m_line_changed = true;\n        if (not (context().flags() & Context::Flags::Draft))\n            m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n    }\n\n    void on_raw_key() override {\n        m_was_interactive = true;\n    }\n\n    void set_prompt_face(Face face)\n    {\n        if (face != m_prompt_face)\n        {\n            m_prompt_face = face;\n            display();\n        }\n    }\n\n    ModeInfo mode_info() const override\n    {\n        return {{ \"prompt\", context().faces()[\"StatusLineMode\"] }, {}};\n    }\n\n    KeymapMode keymap_mode() const override { return KeymapMode::Prompt; }\n\n    StringView name() const override { return \"prompt\"; }\n\nprivate:\n    template<typename Completer>\n    void use_explicit_completer(Completer&& completer)\n    {\n        m_explicit_completer = [completer](const Context& context, StringView content, ByteCount cursor_pos) {\n            Optional<Token> last_token;\n            CommandParser parser{content.substr(0_byte, cursor_pos)};\n            while (auto token = parser.read_token(false))\n                last_token = std::move(token);\n\n            if (last_token and (last_token->pos + last_token->content.length() < cursor_pos))\n                last_token.reset();\n\n            auto token_start = last_token.map([&](auto&& t) { return t.pos; }).value_or(cursor_pos);\n            auto token_content = last_token.map([](auto&& t) -> StringView { return t.content; }).value_or(StringView{});\n\n            return Completions{token_start, cursor_pos, completer(context, token_content)};\n        };\n    }\n\n    void refresh_completions()\n    {\n        try\n        {\n            m_refresh_completion_pending = false;\n            auto& completer = m_explicit_completer ? m_explicit_completer : m_completer;\n            if (not completer)\n                return;\n            m_current_completion = -1;\n            const String& line = m_line_editor.line();\n            m_completions = completer(context(), line,\n                                      line.byte_count_to(m_line_editor.cursor_pos()));\n            if (not context().has_client())\n                return;\n            if (m_completions.candidates.empty())\n                return context().client().menu_hide();\n\n            show_completions();\n            const bool menu = (bool)(m_completions.flags & Completions::Flags::Menu);\n            if (menu)\n                context().client().menu_select(0);\n            auto prefix = line.substr(m_completions.start, m_completions.end - m_completions.start);\n            m_prefix_in_completions = not menu and not contains(m_completions.candidates, prefix);\n            if (m_prefix_in_completions)\n            {\n                m_current_completion = m_completions.candidates.size();\n                m_completions.candidates.push_back(prefix.str());\n            }\n        } catch (runtime_error&) {}\n    }\n\n    void show_completions()\n    {\n        Vector<DisplayLine> items;\n        for (auto& candidate : m_completions.candidates)\n            items.push_back({ candidate, {} });\n\n        const auto menu_style = (m_flags & PromptFlags::Search) ? MenuStyle::Search : MenuStyle::Prompt;\n        context().client().menu_show(std::move(items), {}, menu_style);\n    }\n\n    void clear_completions()\n    {\n        m_current_completion = -1;\n        m_completions.candidates.clear();\n    }\n\n    void display()\n    {\n        if (not context().has_client())\n            return;\n\n        auto width = context().client().dimensions().column - m_prompt.column_length();\n        DisplayLine display_line;\n        if (not (m_flags & PromptFlags::Password))\n            display_line = m_line_editor.build_display_line(width);\n        context().print_status({m_prompt, m_prompt_face}, display_line, m_line_editor.cursor_display_column());\n    }\n\n    void on_enabled(bool from_pop) override\n    {\n        display();\n        if (from_pop)\n        {\n            if (context().has_client() and not m_completions.candidates.empty())\n            {\n                show_completions();\n                const bool menu = (bool)(m_completions.flags & Completions::Flags::Menu);\n                if (m_current_completion != -1)\n                    context().client().menu_select(m_current_completion);\n                else if (menu)\n                    context().client().menu_select(0);\n            }\n        }\n\n        if (not (context().flags() & Context::Flags::Draft))\n            m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n    }\n\n    void on_disabled(bool from_push) override\n    {\n        if (not from_push)\n            context().print_status({});\n\n        m_idle_timer.disable();\n        if (context().has_client())\n            context().client().menu_hide();\n    }\n\n    PromptCallback  m_callback;\n    PromptCompleter m_completer;\n    PromptCompleter m_explicit_completer;\n    const String   m_prompt;\n    Face           m_prompt_face;\n    Completions    m_completions;\n    int            m_current_completion = -1;\n    bool           m_prefix_in_completions = false;\n    String         m_prefix;\n    String         m_empty_text;\n    LineEditor     m_line_editor;\n    bool           m_line_changed = false;\n    PromptFlags    m_flags;\n    bool           m_was_interactive = false;\n    Register&      m_history;\n    int            m_current_history;\n    bool           m_auto_complete;\n    bool           m_refresh_completion_pending = true;\n    Timer          m_idle_timer;\n\n    void history_push(StringView entry)\n    {\n        if (entry.empty() or not m_was_interactive or\n            (m_flags & PromptFlags::DropHistoryEntriesWithBlankPrefix and\n             is_horizontal_blank(entry[0_byte])))\n            return;\n\n        m_history.set(context(), {entry.str()});\n    }\n};\n\nclass NextKey : public InputMode\n{\npublic:\n    NextKey(InputHandler& input_handler, String name, KeymapMode keymap_mode, KeyCallback callback,\n            Timer::Callback idle_callback)\n        : InputMode(input_handler), m_name{std::move(name)}, m_callback(std::move(callback)), m_keymap_mode(keymap_mode),\n          m_idle_timer{Clock::now() + get_idle_timeout(context()), std::move(idle_callback)} {}\n\n    void on_key(Key key) override\n    {\n        // maintain hooks disabled in the callback if they were before pop_mode\n        ScopedSetBool disable_hooks(context().hooks_disabled(),\n                                    context().hooks_disabled());\n        pop_mode();\n        m_callback(key, context());\n    }\n\n    ModeInfo mode_info() const override\n    {\n        return {{ \"enter key\", context().faces()[\"StatusLineMode\"] }, {}};\n    }\n\n    KeymapMode keymap_mode() const override { return m_keymap_mode; }\n\n    StringView name() const override { return m_name; }\n\nprivate:\n    String         m_name;\n    KeyCallback    m_callback;\n    KeymapMode     m_keymap_mode;\n    Timer          m_idle_timer;\n};\n\nclass Insert : public InputMode\n{\npublic:\n    Insert(InputHandler& input_handler, InsertMode mode, int count, Insertion* last_insert)\n        : InputMode(input_handler),\n          m_edition(context()),\n          m_selection_edition(context()),\n          m_completer(context()),\n          m_last_insert(last_insert),\n          m_restore_cursor(mode == InsertMode::Append),\n          m_auto_complete{context().options()[\"autocomplete\"].get<AutoComplete>() & AutoComplete::Insert},\n          m_idle_timer{TimePoint::max(), context().flags() & Context::Flags::Draft ?\n                       Timer::Callback{} : [this](Timer&) {\n                           RefPtr<InputMode> keep_alive{this}; // hook could trigger pop_mode()\n                           if (context().has_client())\n                               context().client().clear_pending();\n                           m_completer.update(m_auto_complete);\n                           context().hooks().run_hook(Hook::InsertIdle, \"\", context());\n                       }},\n          m_disable_hooks{context().hooks_disabled(), context().hooks_disabled()}\n    {\n        context().buffer().throw_if_read_only();\n\n        if (m_last_insert)\n        {\n            m_last_insert->recording.set();\n            m_last_insert->mode = mode;\n            m_last_insert->keys.clear();\n            m_last_insert->disable_hooks = context().hooks_disabled();\n            m_last_insert->count = count;\n        }\n        prepare(mode, count);\n    }\n\n    void on_enabled(bool from_pop) override\n    {\n        if (not (context().flags() & Context::Flags::Draft))\n            m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n    }\n\n    void on_disabled(bool from_push) override\n    {\n        m_idle_timer.disable();\n\n        if (not from_push)\n        {\n            if (m_last_insert)\n                m_last_insert->recording.unset();\n\n            auto& selections = context().selections();\n            if (m_restore_cursor)\n            {\n                for (auto& sel : selections)\n                {\n                    if (sel.cursor() > sel.anchor() and sel.cursor() > BufferCoord{0, 0})\n                        sel.cursor() = context().buffer().char_prev(sel.cursor());\n                }\n            }\n        }\n    }\n\n    void on_key(Key key) override\n    {\n        auto& buffer = context().buffer();\n\n        const bool transient = context().flags() & Context::Flags::Draft;\n        bool update_completions = true;\n        bool moved = false;\n        if (m_mouse_handler.handle_key(key, context()))\n        {\n            if (not transient)\n                m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n        }\n        else if (key == Key::Escape or key == ctrl('c'))\n        {\n            m_completer.reset();\n            pop_mode();\n        }\n        else if (key == Key::Backspace or key == shift(Key::Backspace))\n        {\n            Vector<Selection> sels;\n            for (auto& sel : context().selections())\n            {\n                if (sel.cursor() == BufferCoord{0,0})\n                    continue;\n                auto pos = sel.cursor();\n                sels.emplace_back(buffer.char_prev(pos));\n            }\n            auto& main = context().selections().main();\n            String main_char;\n            if (main.cursor() != BufferCoord{0, 0})\n                main_char = buffer.string(buffer.char_prev(main.cursor()),\n                                          main.cursor());\n            if (not sels.empty())\n                SelectionList{buffer, std::move(sels)}.erase();\n\n            if (not main_char.empty())\n                context().hooks().run_hook(Hook::InsertDelete, main_char, context());\n\n            context().selections_write_only().update(false);\n        }\n        else if (key == Key::Delete)\n        {\n            Vector<Selection> sels;\n            for (auto& sel : context().selections())\n                sels.emplace_back(sel.cursor());\n            SelectionList{buffer, std::move(sels)}.erase();\n        }\n        else if (key == Key::Left)\n        {\n            move(-1_char);\n            moved = true;\n        }\n        else if (key == Key::Right)\n        {\n            move(1_char);\n            moved = true;\n        }\n        else if (key == Key::Up)\n        {\n            move(-1_line);\n            moved = true;\n        }\n        else if (key == Key::Down)\n        {\n            move(1_line);\n            moved = true;\n        }\n        else if (key == Key::Home)\n        {\n            auto& selections = context().selections();\n            for (auto& sel : selections)\n                sel.anchor() = sel.cursor() = BufferCoord{sel.cursor().line, 0};\n            selections.sort_and_merge_overlapping();\n        }\n        else if (key == Key::End)\n        {\n            auto& buffer = context().buffer();\n            auto& selections = context().selections();\n            for (auto& sel : selections)\n            {\n                const LineCount line = sel.cursor().line;\n                sel.anchor() = sel.cursor() = buffer.clamp({line, buffer[line].length()});\n            }\n            selections.sort_and_merge_overlapping();\n        }\n        else if (auto cp = key.codepoint())\n        {\n            m_completer.try_accept();\n            insert(*cp);\n        }\n        else if (key == ctrl('r'))\n        {\n            m_completer.try_accept();\n            on_next_key_with_autoinfo(context(), \"register\", KeymapMode::None,\n                [this](Key key, Context&) {\n                    auto cp = key.codepoint();\n                    if (not cp or key == Key::Escape)\n                        return;\n                    insert(RegisterManager::instance()[*cp].get(context()));\n                }, \"enter register name\", register_doc.str());\n            update_completions = false;\n        }\n        else if (key == ctrl('n') or key == ctrl('p') or key.modifiers == Key::Modifiers::MenuSelect)\n        {\n            drop_last_recorded_key();\n            bool relative = key.modifiers != Key::Modifiers::MenuSelect;\n            int index = relative ? (key == ctrl('n') ? 1 : -1) : key.key;\n            m_completer.select(index, relative, [&](Key key) { record_key(key); });\n            update_completions = false;\n        }\n        else if (key == ctrl('x'))\n        {\n            on_next_key_with_autoinfo(context(), \"explicit-completion\", KeymapMode::None,\n                [this](Key key, Context&) {\n                    if (key.key == 'f')\n                        m_completer.explicit_file_complete();\n                    if (key.key == 'w')\n                        m_completer.explicit_word_buffer_complete();\n                    if (key.key == 'W')\n                        m_completer.explicit_word_all_complete();\n                    if (key.key == 'l')\n                        m_completer.explicit_line_buffer_complete();\n                    if (key.key == 'L')\n                        m_completer.explicit_line_all_complete();\n            }, \"enter completion type\",\n            \"f: filename\\n\"\n            \"w: word (current buffer)\\n\"\n            \"W: word (all buffers)\\n\"\n            \"l: line (current buffer)\\n\"\n            \"L: line (all buffers)\\n\");\n            update_completions = false;\n        }\n        else if (key == ctrl('o'))\n        {\n            m_auto_complete = not m_auto_complete;\n            m_completer.reset();\n        }\n        else if (key == ctrl('u'))\n        {\n            context().buffer().commit_undo_group();\n            context().print_status({ format(\"committed change #{}\",\n                                            (size_t)context().buffer().current_history_id()),\n                                     context().faces()[\"Information\"] });\n        }\n        else if (key == ctrl('v'))\n        {\n            m_completer.try_accept();\n            on_next_key_with_autoinfo(context(), \"raw-insert\", KeymapMode::None,\n                [this, transient](Key key, Context&) {\n                    if (auto cp = get_raw_codepoint(key))\n                    {\n                        insert(*cp);\n                        context().hooks().run_hook(Hook::InsertKey, to_string(key), context());\n                        if (enabled() and not transient)\n                            m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n                    }\n                }, \"raw insert\", \"enter key to insert\");\n            update_completions = false;\n        }\n        else if (key == alt(';'))\n        {\n            push_mode(new Normal(context().input_handler(), true));\n            return;\n        }\n\n        context().hooks().run_hook(Hook::InsertKey, to_string(key), context());\n        if (moved)\n            context().hooks().run_hook(Hook::InsertMove, to_string(key), context());\n\n        if (update_completions and enabled() and not transient) // Hooks might have disabled us\n            m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n    }\n\n    void paste(StringView content) override\n    {\n        m_completer.try_accept();\n        insert(ConstArrayView<StringView>{content});\n        m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context()));\n    }\n\n    ModeInfo mode_info() const override\n    {\n        auto num_sel = context().selections().size();\n        auto main_index = context().selections().main_index();\n        return {{AtomList{ { \"insert\", context().faces()[\"StatusLineMode\"] },\n                           { \" \", context().faces()[\"StatusLine\"] },\n                           { num_sel == 1 ? format(\"{} sel\", num_sel)\n                               : format(\"{} sels ({})\", num_sel, main_index + 1),\n                             context().faces()[\"StatusLineInfo\"] } }},\n                {}};\n    }\n\n    KeymapMode keymap_mode() const override { return KeymapMode::Insert; }\n\n    StringView name() const override { return \"insert\"; }\n\nprivate:\n    template<typename Type>\n    void move(Type offset)\n    {\n        auto& selections = context().selections();\n        const ColumnCount tabstop = context().options()[\"tabstop\"].get<int>();\n        for (auto& sel : selections)\n        {\n            auto cursor = context().buffer().offset_coord(sel.cursor(), offset, tabstop);\n            sel.anchor() = sel.cursor() = cursor;\n        }\n        selections.sort_and_merge_overlapping();\n    }\n\n    template<typename S>\n    void insert(ConstArrayView<S> strings)\n    {\n        kak_assert(not m_completer.has_candidate_selected());\n        context().selections().for_each([strings, &buffer=context().buffer()]\n                                        (size_t index, Selection& sel) {\n            Kakoune::insert(buffer, sel, sel.cursor(), strings[std::min(strings.size()-1, index)]);\n        }, false);\n    }\n\n    void insert(Codepoint key)\n    {\n        String str{key};\n        insert(ConstArrayView<String>{str});\n        context().hooks().run_hook(Hook::InsertChar, str, context());\n    }\n\n    void prepare(InsertMode mode, int count)\n    {\n        SelectionList& selections = context().selections();\n        Buffer& buffer = context().buffer();\n\n        switch (mode)\n        {\n        case InsertMode::Insert:\n            for (auto& sel : selections)\n                sel.set(sel.max(), sel.min());\n            break;\n        case InsertMode::Replace:\n            selections.erase();\n            break;\n        case InsertMode::Append:\n            for (auto& sel : selections)\n            {\n                sel.set(sel.min(),  buffer.char_next(sel.max()));\n                if (sel.cursor() == buffer.end_coord())\n                    buffer.insert(buffer.end_coord(), \"\\n\");\n            }\n            break;\n        case InsertMode::AppendAtLineEnd:\n            for (auto& sel : selections)\n                sel.set({sel.max().line, buffer[sel.max().line].length() - 1});\n            break;\n        case InsertMode::OpenLineBelow:\n        {\n            Vector<Selection> new_sels;\n            count = count > 0 ? count : 1;\n            LineCount inserted_count = 0;\n            for (auto sel : selections)\n            {\n                buffer.insert(sel.max().line + inserted_count + 1,\n                              String{'\\n', CharCount{count}});\n                for (int i = 0; i < count; ++i)\n                    new_sels.push_back(BufferCoord{sel.max().line + inserted_count + i + 1});\n                inserted_count += count;\n            }\n            selections.set(std::move(new_sels),\n                           selections.main_index() * count + count - 1);\n            context().hooks().run_hook(Hook::InsertChar, \"\\n\", context());\n            break;\n        }\n        case InsertMode::OpenLineAbove:\n        {\n            Vector<Selection> new_sels;\n            count = count > 0 ? count : 1;\n            LineCount inserted_count = 0;\n            for (auto sel : selections)\n            {\n                buffer.insert(sel.min().line + inserted_count,\n                              String{'\\n', CharCount{count}});\n                for (int i = 0; i < count; ++i)\n                    new_sels.push_back(BufferCoord{sel.min().line + inserted_count + i});\n                inserted_count += count;\n            }\n            selections.set(std::move(new_sels),\n                           selections.main_index() * count + count - 1);\n            context().hooks().run_hook(Hook::InsertChar, \"\\n\", context());\n            break;\n        }\n        case InsertMode::InsertAtLineBegin:\n            for (auto& sel : selections)\n            {\n                BufferCoord pos = sel.min().line;\n                auto pos_non_blank = buffer.iterator_at(pos);\n                while (*pos_non_blank == ' ' or *pos_non_blank == '\\t')\n                    ++pos_non_blank;\n                if (*pos_non_blank != '\\n')\n                    pos = pos_non_blank.coord();\n                sel.set(pos);\n            }\n            break;\n        }\n        selections.check_invariant();\n        buffer.check_invariant();\n    }\n\n    ScopedEdition   m_edition;\n    ScopedSelectionEdition m_selection_edition;\n    InsertCompleter m_completer;\n    Insertion*      m_last_insert;\n    const bool      m_restore_cursor;\n    bool            m_auto_complete;\n    Timer           m_idle_timer;\n    MouseHandler    m_mouse_handler;\n    ScopedSetBool   m_disable_hooks;\n};\n\n}\n\nInputHandler::InputHandler(SelectionList selections, Context::Flags flags, String name)\n    : m_context(*this, std::move(selections), flags, std::move(name))\n{\n    m_mode_stack.emplace_back(new InputModes::Normal(*this));\n    current_mode().on_enabled(false);\n}\n\nInputHandler::~InputHandler() = default;\n\nvoid InputHandler::push_mode(InputMode* new_mode)\n{\n    StringView prev_name = current_mode().name();\n\n    current_mode().on_disabled(true);\n    m_mode_stack.emplace_back(new_mode);\n    new_mode->on_enabled(false);\n\n    context().hooks().run_hook(Hook::ModeChange, format(\"push:{}:{}\", prev_name, new_mode->name()), context());\n}\n\nvoid InputHandler::pop_mode(InputMode* mode)\n{\n    kak_assert(m_mode_stack.back().get() == mode);\n    kak_assert(m_mode_stack.size() > 1);\n\n    RefPtr<InputMode> keep_alive{mode}; // Ensure prev_name stays valid\n    StringView prev_name = mode->name();\n\n    current_mode().on_disabled(false);\n    m_mode_stack.pop_back();\n    current_mode().on_enabled(true);\n\n    context().hooks().run_hook(Hook::ModeChange, format(\"pop:{}:{}\", prev_name, current_mode().name()), context());\n}\n\nvoid InputHandler::reset_normal_mode()\n{\n    kak_assert(dynamic_cast<InputModes::Normal*>(m_mode_stack[0].get()) != nullptr);\n    if (m_mode_stack.size() == 1)\n        return;\n\n    while (m_mode_stack.size() > 1)\n        pop_mode(m_mode_stack.back().get());\n}\n\nvoid InputHandler::insert(InsertMode mode, int count)\n{\n    push_mode(new InputModes::Insert(*this, mode, count, m_handle_key_level <= 1 ? &m_last_insert : nullptr));\n}\n\nvoid InputHandler::repeat_last_insert()\n{\n    if (m_last_insert.keys.empty())\n        return;\n\n    if (dynamic_cast<InputModes::Normal*>(&current_mode()) == nullptr or\n        m_last_insert.recording)\n        throw runtime_error{\"repeating last insert not available in this context\"};\n\n    ScopedSetBool disable_hooks(context().hooks_disabled(),\n                                m_last_insert.disable_hooks);\n\n    push_mode(new InputModes::Insert(*this, m_last_insert.mode, m_last_insert.count, nullptr));\n    for (auto& key : m_last_insert.keys)\n        handle_key(key);\n    kak_assert(dynamic_cast<InputModes::Normal*>(&current_mode()) != nullptr);\n}\n\nvoid InputHandler::paste(StringView content)\n{\n    current_mode().paste(content);\n}\n\nvoid InputHandler::prompt(StringView prompt, String initstr, String emptystr,\n                          Face prompt_face, PromptFlags flags, char history_register,\n                          PromptCompleter completer, PromptCallback callback)\n{\n    push_mode(new InputModes::Prompt(*this, prompt, std::move(initstr), std::move(emptystr),\n                                     prompt_face, flags, history_register,\n                                     std::move(completer), std::move(callback)));\n}\n\nvoid InputHandler::set_prompt_face(Face prompt_face)\n{\n    if (auto* prompt = dynamic_cast<InputModes::Prompt*>(&current_mode()))\n        prompt->set_prompt_face(prompt_face);\n}\n\nvoid InputHandler::on_next_key(StringView mode_name, KeymapMode keymap_mode, KeyCallback callback,\n                               Timer::Callback idle_callback)\n{\n    push_mode(new InputModes::NextKey(*this, format(\"next-key[{}]\", mode_name), keymap_mode, std::move(callback),\n                                      std::move(idle_callback)));\n}\n\nInputHandler::ScopedForceNormal::ScopedForceNormal(InputHandler& handler, NormalParams params)\n    : m_handler(handler), m_mode(nullptr)\n{\n    if (handler.m_mode_stack.size() != 1)\n    {\n        handler.push_mode(new InputModes::Normal(handler));\n        m_mode = handler.m_mode_stack.back().get();\n    }\n\n    static_cast<InputModes::Normal*>(handler.m_mode_stack.back().get())->m_params = params;\n}\n\nInputHandler::ScopedForceNormal::~ScopedForceNormal()\n{\n    if (not m_mode)\n        return;\n\n    if (m_mode == m_handler.m_mode_stack.back().get())\n        m_handler.pop_mode(m_mode);\n    else if (auto it = find(m_handler.m_mode_stack, m_mode);\n             it != m_handler.m_mode_stack.end())\n        m_handler.m_mode_stack.erase(it);\n}\n\nstatic bool is_valid(Key key)\n{\n    constexpr Key::Modifiers valid_mods = (Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift);\n\n    return ((key.modifiers & ~valid_mods) or key.key <= 0x10FFFF);\n}\n\nvoid InputHandler::handle_key(Key key, bool synthesized)\n{\n    if (not is_valid(key))\n        return;\n\n    ++m_handle_key_level;\n    auto dec = OnScopeEnd([this]{ --m_handle_key_level;} );\n\n    if (not synthesized)\n        current_mode().on_raw_key();\n\n    auto process_key = [this](Key k) {\n        record_key(k);\n        current_mode().handle_key(k);\n    };\n\n    const auto keymap_mode = current_mode().keymap_mode();\n    KeymapManager& keymaps = m_context.keymaps();\n    if (keymaps.is_mapped(key, keymap_mode) and not m_context.keymaps_disabled())\n    {\n        for (auto& k : keymaps.get_mapping_keys(key, keymap_mode))\n            process_key(k);\n    }\n    else\n        process_key(key);\n\n    if (m_handle_key_level < m_recording_level)\n    {\n        write_to_debug_buffer(\"Macro recording started but not finished\");\n        m_recording_reg = 0;\n        m_recording_level = -1;\n    }\n}\n\nvoid InputHandler::record_key(Key key)\n{\n    if (m_last_insert.recording and m_handle_key_level <= 1)\n        m_last_insert.keys.push_back(key);\n    if (is_recording() and m_handle_key_level == m_recording_level)\n        m_recorded_keys.push_back(key);\n}\n\nvoid InputHandler::drop_last_recorded_key()\n{\n    if (m_last_insert.recording and m_handle_key_level <= 1)\n    {\n        kak_assert(not m_last_insert.keys.empty());\n        m_last_insert.keys.pop_back();\n    }\n\n    if (is_recording() and m_handle_key_level == m_recording_level)\n    {\n        kak_assert(not m_recorded_keys.empty());\n        m_recorded_keys.pop_back();\n    }\n}\n\nvoid InputHandler::refresh_ifn()\n{\n    current_mode().refresh_ifn();\n}\n\nvoid InputHandler::start_recording(char reg)\n{\n    kak_assert(m_recording_reg == 0);\n    m_recording_level = m_handle_key_level;\n    m_recorded_keys.clear();\n    m_recording_reg = reg;\n}\n\nbool InputHandler::is_recording() const\n{\n    return m_recording_reg != 0;\n}\n\nvoid InputHandler::stop_recording()\n{\n    kak_assert(m_recording_reg != 0);\n    if (not m_recorded_keys.empty())\n    {\n        // Forget the key that got us to exit recording\n        if (m_handle_key_level == m_recording_level)\n            m_recorded_keys.pop_back();\n\n        String keys;\n        for (auto& key : m_recorded_keys)\n            keys += to_string(key);\n        RegisterManager::instance()[m_recording_reg].set(context(), {keys});\n    }\n\n    m_recording_reg = 0;\n    m_recording_level = -1;\n}\n\nModeInfo InputHandler::mode_info() const\n{\n    return current_mode().mode_info();\n}\n\nbool should_show_info(AutoInfo mask, const Context& context)\n{\n  return (context.options()[\"autoinfo\"].get<AutoInfo>() & mask) and context.has_client();\n}\n\nbool show_auto_info_ifn(StringView title, StringView info, AutoInfo mask, const Context& context)\n{\n    if (not should_show_info(mask, context))\n        return false;\n\n    context.client().info_show(title.str(), info.str(), {}, InfoStyle::Prompt);\n    return true;\n}\n\nvoid hide_auto_info_ifn(const Context& context, bool hide)\n{\n    if (hide)\n        context.client().info_hide();\n}\n\nvoid scroll_window(Context& context, LineCount offset, OnHiddenCursor on_hidden_cursor)\n{\n    Window& window = context.window();\n    Buffer& buffer = context.buffer();\n    const LineCount line_count = buffer.line_count();\n\n    DisplayCoord win_pos = window.position();\n    DisplayCoord win_dim = window.dimensions();\n\n    if (on_hidden_cursor == OnHiddenCursor::PreserveSelections)\n        context.ensure_cursor_visible = false;\n\n    if ((offset < 0 and win_pos.line == 0) or (offset > 0 and win_pos.line == line_count - 1))\n        return;\n\n    const DisplayCoord max_offset{(win_dim.line - 1)/2, (win_dim.column - 1)/2};\n    const DisplayCoord scrolloff =\n        std::min(context.options()[\"scrolloff\"].get<DisplayCoord>(), max_offset);\n\n    win_pos.line = clamp(win_pos.line + offset, 0_line, line_count-1);\n\n    window.set_position(win_pos);\n    if (on_hidden_cursor != OnHiddenCursor::PreserveSelections)\n    {\n        ScopedSelectionEdition selection_edition{context};\n        SelectionList& selections = context.selections();\n        Selection& main_selection = selections.main();\n        const BufferCoord anchor = main_selection.anchor();\n        const BufferCoordAndTarget cursor = main_selection.cursor();\n\n        auto cursor_off = win_pos.line - window.position().line;\n\n        auto line = clamp(cursor.line + cursor_off, win_pos.line + scrolloff.line,\n                          win_pos.line + win_dim.line - 1 - scrolloff.line);\n\n        const ColumnCount tabstop = context.options()[\"tabstop\"].get<int>();\n        auto new_cursor = buffer.offset_coord(cursor, line - cursor.line, tabstop);\n\n        main_selection = {on_hidden_cursor == OnHiddenCursor::MoveCursor ? anchor : new_cursor, new_cursor};\n\n        selections.sort_and_merge_overlapping();\n    }\n}\n\n}\n"
  },
  {
    "path": "src/input_handler.hh",
    "content": "#ifndef input_handler_hh_INCLUDED\n#define input_handler_hh_INCLUDED\n\n#include \"completion.hh\"\n#include \"array.hh\"\n#include \"context.hh\"\n#include \"env_vars.hh\"\n#include \"enum.hh\"\n#include \"face.hh\"\n#include \"normal.hh\"\n#include \"optional.hh\"\n#include \"keys.hh\"\n#include \"string.hh\"\n#include \"utils.hh\"\n#include \"safe_ptr.hh\"\n#include \"display_buffer.hh\"\n\nnamespace Kakoune\n{\n\nenum class PromptEvent\n{\n    Change,\n    Abort,\n    Validate\n};\nusing PromptCallback = MoveOnlyFunction<void (StringView, PromptEvent, Context&)>;\n\nenum class PromptFlags\n{\n    None = 0,\n    Password = 1 << 0,\n    DropHistoryEntriesWithBlankPrefix = 1 << 1,\n    Search = 1 << 2,\n};\nconstexpr bool with_bit_ops(Meta::Type<PromptFlags>) { return true; }\n\nusing KeyCallback = Function<void (Key, Context&)>;\n\nclass InputMode;\nenum class KeymapMode : char;\nenum class CursorMode;\n\nclass Timer;\n\nusing PromptCompleter = Function<Completions (const Context&, StringView, ByteCount)>;\nenum class InsertMode : unsigned\n{\n    Insert,\n    Append,\n    Replace,\n    InsertAtLineBegin,\n    AppendAtLineEnd,\n    OpenLineBelow,\n    OpenLineAbove\n};\n\nstruct ModeInfo\n{\n    DisplayLine display_line;\n    Optional<NormalParams> normal_params;\n};\n\nclass InputHandler : public SafeCountable\n{\npublic:\n    InputHandler(SelectionList selections,\n                 Context::Flags flags = Context::Flags::None,\n                 String name = \"\");\n    ~InputHandler();\n\n    // switch to insert mode\n    void insert(InsertMode mode, int count);\n    // repeat last insert mode key sequence\n    void repeat_last_insert();\n    // insert a string without affecting the mode stack\n    void paste(StringView content);\n\n    // enter prompt mode, callback is called on each change,\n    // abort or validation with corresponding PromptEvent value\n    // returns to normal mode after validation if callback does\n    // not change the mode itself\n    void prompt(StringView prompt, String initstr, String emptystr,\n                Face prompt_face, PromptFlags flags, char history_register,\n                PromptCompleter completer, PromptCallback callback);\n    void set_prompt_face(Face prompt_face);\n    bool history_enabled() const;\n\n    // execute callback on next keypress and returns to normal mode\n    // if callback does not change the mode itself\n    void on_next_key(StringView mode_name, KeymapMode mode, KeyCallback callback,\n                     Function<void (Timer& timer)> idle_callback = {});\n\n    // process the given key\n    void handle_key(Key key, bool synthesized = true);\n\n    void refresh_ifn();\n\n    void start_recording(char reg);\n    bool is_recording() const;\n    void stop_recording();\n    char recording_reg() const { return m_recording_reg; }\n\n    void reset_normal_mode();\n\n    Context& context() { return m_context; }\n    const Context& context() const { return m_context; }\n\n    ModeInfo mode_info() const;\n\n    // Force an input handler into normal mode temporarily\n    struct ScopedForceNormal\n    {\n        ScopedForceNormal(InputHandler& handler, NormalParams params);\n        ~ScopedForceNormal();\n\n    private:\n        InputHandler& m_handler;\n        InputMode* m_mode;\n    };\n\nprivate:\n    Context m_context;\n\n    friend class InputMode;\n    Vector<RefPtr<InputMode>, MemoryDomain::Client> m_mode_stack;\n\n    InputMode& current_mode() const { return *m_mode_stack.back(); }\n\n    void push_mode(InputMode* new_mode);\n    void pop_mode(InputMode* current_mode);\n\n    void record_key(Key key);\n    void drop_last_recorded_key();\n\n    struct Insertion{\n        NestedBool recording;\n        InsertMode mode;\n        Vector<Key> keys;\n        bool disable_hooks;\n        int count;\n    } m_last_insert = { {}, InsertMode::Insert, {}, false, 1 };\n\n    int m_handle_key_level = 0;\n\n    char        m_recording_reg = 0;\n    Vector<Key> m_recorded_keys;\n    int         m_recording_level = -1;\n};\n\nenum class AutoInfo\n{\n    None = 0,\n    Command = 1 << 0,\n    OnKey   = 1 << 1,\n    Normal  = 1 << 2\n};\n\nconstexpr bool with_bit_ops(Meta::Type<AutoInfo>) { return true; }\n\nconstexpr auto enum_desc(Meta::Type<AutoInfo>)\n{\n    return make_array<EnumDesc<AutoInfo>>({\n        { AutoInfo::Command, \"command\"},\n        { AutoInfo::OnKey, \"onkey\"},\n        { AutoInfo::Normal, \"normal\" }\n    });\n}\n\nenum class AutoComplete\n{\n    None = 0,\n    Insert = 0b01,\n    Prompt = 0b10\n};\nconstexpr bool with_bit_ops(Meta::Type<AutoComplete>) { return true; }\n\nconstexpr auto enum_desc(Meta::Type<AutoComplete>)\n{\n    return make_array<EnumDesc<AutoComplete>>({\n        { AutoComplete::Insert, \"insert\"},\n        { AutoComplete::Prompt, \"prompt\" }\n    });\n}\n\nbool should_show_info(AutoInfo mask, const Context& context);\nbool show_auto_info_ifn(StringView title, StringView info, AutoInfo mask, const Context& context);\nvoid hide_auto_info_ifn(const Context& context, bool hide);\n\ntemplate<typename Cmd>\nvoid on_next_key_with_autoinfo(const Context& context, StringView mode_name,\n                               KeymapMode keymap_mode, Cmd cmd,\n                               String title, String info)\n{\n    context.input_handler().on_next_key(mode_name,\n        keymap_mode, [cmd](Key key, Context& context) mutable {\n            bool hide = should_show_info(AutoInfo::OnKey, context);\n            hide_auto_info_ifn(context, hide);\n            cmd(key, context);\n        }, [&context, title=std::move(title), info=std::move(info)](Timer&) {\n           show_auto_info_ifn(title, info, AutoInfo::OnKey, context);\n        });\n}\n\nenum class OnHiddenCursor {\n    PreserveSelections,\n    MoveCursor,\n    MoveCursorAndAnchor,\n};\n\nvoid scroll_window(Context& context, LineCount offset, OnHiddenCursor on_hidden_cursor);\n\n}\n\n#endif // input_handler_hh_INCLUDED\n"
  },
  {
    "path": "src/insert_completer.cc",
    "content": "#include \"insert_completer.hh\"\n\n#include \"buffer_manager.hh\"\n#include \"buffer_utils.hh\"\n#include \"debug.hh\"\n#include \"command_manager.hh\"\n#include \"changes.hh\"\n#include \"context.hh\"\n#include \"client.hh\"\n#include \"display_buffer.hh\"\n#include \"face_registry.hh\"\n#include \"file.hh\"\n#include \"hook_manager.hh\"\n#include \"option_manager.hh\"\n#include \"regex.hh\"\n#include \"window.hh\"\n#include \"word_db.hh\"\n#include \"word_splitter.hh\"\n#include \"option_types.hh\"\n#include \"utf8_iterator.hh\"\n#include \"user_interface.hh\"\n\n#include <utility>\n\nnamespace Kakoune\n{\n\nusing StringList = Vector<String, MemoryDomain::Options>;\n\nString option_to_string(const InsertCompleterDesc& opt)\n{\n    switch (opt.mode)\n    {\n        case InsertCompleterDesc::Word:\n            return \"word=\" + (opt.param ? *opt.param : \"\");\n        case InsertCompleterDesc::Filename:\n            return \"filename\";\n        case InsertCompleterDesc::Option:\n            return \"option=\" + (opt.param ? *opt.param : \"\");\n        case InsertCompleterDesc::Line:\n            return \"line=\" + (opt.param ? *opt.param : \"\");\n    }\n    kak_assert(false);\n    return \"\";\n}\n\nInsertCompleterDesc option_from_string(Meta::Type<InsertCompleterDesc>, StringView str)\n{\n    if (str.substr(0_byte, 7_byte) == \"option=\")\n        return {InsertCompleterDesc::Option, str.substr(7_byte).str()};\n    else if (str.substr(0_byte, 5_byte) == \"word=\")\n    {\n        auto param = str.substr(5_byte);\n        if (param == \"all\" or param == \"buffer\")\n            return {InsertCompleterDesc::Word, param.str()};\n    }\n    else if (str == \"filename\")\n        return {InsertCompleterDesc::Filename, {}};\n    else if (str.substr(0_byte, 5_byte) == \"line=\")\n    {\n        auto param = str.substr(5_byte);\n        if (param == \"all\" or param == \"buffer\")\n            return {InsertCompleterDesc::Line, param.str()};\n    }\n    throw runtime_error(format(\"invalid completer description: '{}'\", str));\n}\n\nnamespace\n{\n\ntemplate<bool other_buffers>\nInsertCompletion complete_word(const SelectionList& sels,\n                               const OptionManager& options,\n                               const FaceRegistry& faces)\n{\n    ConstArrayView<Codepoint> extra_word_chars = options[\"extra_word_chars\"].get<Vector<Codepoint, MemoryDomain::Options>>();\n    auto is_word_pred = [extra_word_chars](Codepoint c) { return is_word(c, extra_word_chars); };\n\n    const Buffer& buffer = sels.buffer();\n    BufferCoord cursor_pos = sels.main().cursor();\n\n    using Utf8It = utf8::iterator<BufferIterator>;\n    Utf8It pos{buffer.iterator_at(cursor_pos), buffer};\n    if (pos == buffer.begin() or not is_word_pred(*(pos-1)))\n        return {};\n\n    BufferCoord word_begin;\n    StringView prefix;\n    HashMap<StringView, int> sel_word_counts;\n    for (int i = 0; i < sels.size(); ++i)\n    {\n        int len = 0;\n        auto is_short_enough_word = [&] (Codepoint c) { return len++ < WordSplitter::max_word_len && is_word_pred(c); };\n\n        Utf8It end{buffer.iterator_at(sels[i].cursor()), buffer};\n        Utf8It begin = end-1;\n        if (not skip_while_reverse(begin, buffer.begin(), is_short_enough_word) and\n            begin < end) // (begin might == end if end == buffer.begin())\n            ++begin;\n\n        if (i == sels.main_index())\n        {\n            word_begin = begin.base().coord();\n            prefix = buffer.substr(word_begin, end.base().coord());\n        }\n\n        skip_while(end, buffer.end(), is_short_enough_word);\n\n        if (len <= WordSplitter::max_word_len)\n        {\n            StringView word = buffer.substr(begin.base().coord(), end.base().coord());\n            ++sel_word_counts[word];\n        }\n    }\n\n    struct RankedMatchAndBuffer : RankedMatch\n    {\n        RankedMatchAndBuffer(RankedMatch  m, const Buffer* b)\n            : RankedMatch{std::move(m)}, buffer{b} {}\n\n        using RankedMatch::operator==;\n        using RankedMatch::operator<;\n        bool operator==(StringView other) const { return candidate() == other; }\n\n        const Buffer* buffer;\n    };\n\n    auto& word_db = get_word_db(buffer);\n    auto matches = word_db.find_matching(prefix)\n                 | transform([&](auto& m) { return RankedMatchAndBuffer{m, &buffer}; })\n                 | gather<Vector<RankedMatchAndBuffer>>();\n    // Remove words that are being edited\n    for (auto& word_count : sel_word_counts)\n    {\n        if (word_db.get_word_occurences(word_count.key) <= word_count.value)\n            unordered_erase(matches, word_count.key);\n    }\n\n    if (other_buffers)\n    {\n        for (const auto& buf : BufferManager::instance())\n        {\n            if (buf.get() == &buffer or buf->flags() & Buffer::Flags::Debug)\n                continue;\n            for (auto& m : get_word_db(*buf).find_matching(prefix) |\n                           // filter out words that are not considered words for the current buffer\n                           filter([&](auto& rm) {\n                               auto&& c = rm.candidate();\n                               return std::all_of(utf8::iterator{c.begin(), c},\n                                                  utf8::iterator{c.end(), c},\n                                                  is_word_pred); }))\n                matches.push_back({ m, buf.get() });\n        }\n    }\n\n    using StaticWords = Vector<String, MemoryDomain::Options>;\n    for (auto& word : options[\"static_words\"].get<StaticWords>())\n        if (RankedMatch match{word, prefix})\n            matches.emplace_back(match, nullptr);\n\n    unordered_erase(matches, prefix);\n    const auto longest = accumulate(matches, 0_char,\n                                    [](const CharCount& lhs, const RankedMatchAndBuffer& rhs)\n                                    { return std::max(lhs, rhs.candidate().char_length()); });\n\n    auto limit = [](StringView s, ColumnCount l) { return s.column_length() <= l ? s.str() : \"…\" + s.substr(s.column_length() - (l + 1)); };\n    constexpr size_t max_count = 100;\n    // Gather best max_count matches\n    InsertCompletion::CandidateList candidates;\n    candidates.reserve(std::min(matches.size(), max_count) + 1);\n\n    for_n_best(matches, max_count, [](auto& lhs, auto& rhs) { return rhs < lhs; },\n               [&](RankedMatchAndBuffer& m) {\n        if (not candidates.empty() and candidates.back().completion == m.candidate())\n            return false;\n        DisplayLine menu_entry;\n        if (other_buffers && m.buffer)\n        {\n            const auto pad_len = longest + 1 - m.candidate().char_length();\n            menu_entry.push_back({ m.candidate().str(), {} });\n            menu_entry.push_back({ String{' ', pad_len}, {} });\n            menu_entry.push_back({ limit(m.buffer->display_name(), 20), faces[\"MenuInfo\"] });\n        }\n        else\n            menu_entry.push_back({ m.candidate().str(), {} });\n\n        candidates.push_back({m.candidate().str(), \"\", std::move(menu_entry)});\n        return true;\n    });\n\n    return { std::move(candidates), word_begin, cursor_pos, buffer.timestamp() };\n}\n\ntemplate<bool require_slash>\nInsertCompletion complete_filename(const SelectionList& sels,\n                                   const OptionManager& options,\n                                   const FaceRegistry&)\n{\n    const Buffer& buffer = sels.buffer();\n    auto pos = buffer.iterator_at(sels.main().cursor());\n    auto begin = pos;\n\n    auto is_filename = [](char c)\n    {\n        return isalnum(c) or c == '/' or c == '.' or c == '_' or c == '-';\n    };\n    while (begin != buffer.begin() and is_filename(*(begin-1)))\n        --begin;\n\n    if (begin != buffer.begin() and *begin == '/' and *(begin-1) == '~')\n        --begin;\n\n    StringView prefix = buffer.substr(begin.coord(), pos.coord());\n    if (require_slash and not contains(prefix, '/'))\n        return {};\n\n    // Do not try to complete in that case as its unlikely to be a filename,\n    // and triggers network host search of cygwin.\n    if (prefix.substr(0_byte, 2_byte) == \"//\")\n        return {};\n\n    InsertCompletion::CandidateList candidates;\n    if (prefix.front() == '/' or prefix.front() == '~')\n    {\n        for (auto& filename : Kakoune::complete_filename(prefix,\n                                                         options[\"ignored_files\"].get<Regex>()))\n            candidates.push_back({ filename, \"\", {filename, {}} });\n    }\n    else\n    {\n        Vector<String> visited_dirs;\n        for (auto dir : options[\"path\"].get<StringList>())\n        {\n            dir = real_path(parse_filename(dir, (buffer.flags() & Buffer::Flags::File) ?\n                                           split_path(buffer.filename()).first : StringView{}));\n\n            if (not dir.empty() and dir.back() != '/')\n                dir += '/';\n\n            if (contains(visited_dirs, dir))\n                continue;\n\n            for (auto& filename : Kakoune::complete_filename(dir + prefix,\n                                                             options[\"ignored_files\"].get<Regex>()))\n            {\n                StringView candidate = filename.substr(dir.length());\n                candidates.push_back({ candidate.str(), \"\", {candidate.str(), {}} });\n            }\n\n            visited_dirs.push_back(std::move(dir));\n        }\n    }\n    if (candidates.empty())\n        return {};\n    return { std::move(candidates), begin.coord(), pos.coord(), buffer.timestamp() };\n}\n\nInsertCompletion complete_option(const SelectionList& sels,\n                                 const OptionManager& options,\n                                 const FaceRegistry& faces,\n                                 StringView option_name)\n{\n    const Buffer& buffer = sels.buffer();\n    BufferCoord cursor_pos = sels.main().cursor();\n\n    const CompletionList& opt = options[option_name].get<CompletionList>();\n    if (opt.list.empty())\n        return {};\n\n    auto& desc = opt.prefix;\n    static const Regex re(R\"((\\d+)\\.(\\d+)(?:\\+(\\d+))?@(\\d+))\");\n    MatchResults<String::const_iterator> match;\n    if (not regex_match(desc.begin(), desc.end(), match, re))\n        return {};\n\n    BufferCoord coord{str_to_int({match[1].first, match[1].second}) - 1,\n                      str_to_int({match[2].first, match[2].second}) - 1};\n    if (not buffer.is_valid(coord))\n        return {};\n    size_t timestamp = (size_t)str_to_int({match[4].first, match[4].second});\n    auto changes = buffer.changes_since(timestamp);\n    if (any_of(changes, [&](auto&& change) { return change.begin < coord; }))\n        return {};\n\n    if (cursor_pos.line != coord.line or cursor_pos.column < coord.column)\n        return {};\n\n    const ColumnCount tabstop = options[\"tabstop\"].get<int>();\n    const ColumnCount column = get_column(buffer, tabstop, cursor_pos);\n\n    struct RankedMatchAndInfo : RankedMatch\n    {\n        using RankedMatch::RankedMatch;\n        using RankedMatch::operator==;\n        using RankedMatch::operator<;\n\n        StringView on_select;\n        DisplayLine menu_entry;\n    };\n\n    StringView query = buffer.substr(coord, cursor_pos);\n    Vector<RankedMatchAndInfo> matches;\n\n    for (auto&& [i, candidate] : opt.list | enumerate())\n    {\n        if (RankedMatchAndInfo match{std::get<0>(candidate), query})\n        {\n            match.set_input_sequence_number(i);\n            match.on_select = std::get<1>(candidate);\n            auto& menu = std::get<2>(candidate);\n            match.menu_entry = not menu.empty() ?\n                parse_display_line(expand_tabs(menu, tabstop, column), faces)\n              : DisplayLine{String{}, {}};\n\n            matches.push_back(std::move(match));\n        }\n    }\n\n    constexpr size_t max_count = 100;\n    // Gather best max_count matches\n    InsertCompletion::CandidateList candidates;\n    candidates.reserve(std::min(matches.size(), max_count) + 1);\n\n    for_n_best(matches, max_count, [](auto& lhs, auto& rhs) { return rhs < lhs; },\n               [&](RankedMatchAndInfo& m) {\n        if (not candidates.empty()\n            and candidates.back().completion == m.candidate()\n            and candidates.back().on_select == m.on_select)\n            return false;\n        candidates.push_back({ m.candidate().str(), m.on_select.str(),\n                               std::move(m.menu_entry) });\n        return true;\n    });\n\n    auto end = cursor_pos;\n    if (match[3].matched)\n    {\n        ByteCount len = str_to_int({match[3].first, match[3].second});\n        end = buffer.advance(coord, len);\n    }\n    return { std::move(candidates), coord, end, timestamp };\n}\n\ntemplate<bool other_buffers>\nInsertCompletion complete_line(const SelectionList& sels,\n                               const OptionManager& options,\n                               const FaceRegistry&)\n{\n    const Buffer& buffer = sels.buffer();\n    BufferCoord cursor_pos = sels.main().cursor();\n\n    const ColumnCount tabstop = options[\"tabstop\"].get<int>();\n    const ColumnCount column = get_column(buffer, tabstop, cursor_pos);\n\n    auto trim_leading_whitespaces = [](StringView s) {\n        utf8::iterator it{s.begin(), s};\n        while (it != s.end() and is_horizontal_blank(*it))\n            ++it;\n        return StringView{it.base(), s.end()};\n    };\n\n    StringView prefix = trim_leading_whitespaces(buffer[cursor_pos.line].substr(0_byte, cursor_pos.column));\n    BufferCoord replace_begin = buffer.advance(cursor_pos, -prefix.length());\n    InsertCompletion::CandidateList candidates;\n\n    auto add_candidates = [&](const Buffer& buf) {\n        for (LineCount l = 0_line; l < buf.line_count(); ++l)\n        {\n            if (&buf == &buffer and l == cursor_pos.line)\n                continue;\n\n            StringView line = buf[l];\n            StringView candidate = trim_leading_whitespaces(line.substr(0_byte, line.length()-1));\n\n            if (candidate.length() == 0)\n              continue;\n\n            if (prefix == candidate.substr(0_byte, prefix.length()))\n            {\n                candidates.push_back({candidate.str(), \"\", {expand_tabs(candidate, tabstop, column), {}} });\n                // perf: it's unlikely the user intends to search among >10 candidates anyway\n                if (candidates.size() == 100)\n                    break;\n            }\n        }\n    };\n\n    add_candidates(buffer);\n\n    if (other_buffers)\n    {\n        for (const auto& buf : BufferManager::instance())\n        {\n            if (buf.get() != &buffer and not (buf->flags() & Buffer::Flags::Debug))\n                add_candidates(*buf);\n        }\n    }\n\n    if (candidates.empty())\n        return {};\n    std::sort(candidates.begin(), candidates.end());\n    candidates.erase(std::unique(candidates.begin(), candidates.end()), candidates.end());\n    return { std::move(candidates), replace_begin, cursor_pos, buffer.timestamp() };\n}\n\n}\n\nInsertCompleter::InsertCompleter(Context& context)\n    : m_context(context),\n      // local scopes might go away before completion ends, make sure to register on a long lived one\n      m_options(context.scope(false).options()),\n      m_faces(context.scope(false).faces())\n{\n    m_options.register_watcher(*this);\n}\n\nInsertCompleter::~InsertCompleter()\n{\n    m_options.unregister_watcher(*this);\n}\n\nvoid InsertCompleter::select(int index, bool relative, FunctionRef<void (Key)> record_key)\n{\n    m_enabled = true;\n    if (not setup_ifn())\n        return;\n\n    auto& buffer = m_context.buffer();\n    m_current_candidate = (relative ? m_current_candidate + index : index) % (int)m_completions.candidates.size();\n    if (m_current_candidate < 0)\n        m_current_candidate += m_completions.candidates.size();\n    const InsertCompletion::Candidate& candidate = m_completions.candidates[m_current_candidate];\n    auto& selections = m_context.selections();\n    const auto& cursor_pos = selections.main().cursor();\n    const auto prefix_len = buffer.distance(m_completions.begin, cursor_pos);\n    const auto suffix_len = std::max(0_byte, buffer.distance(cursor_pos, m_completions.end));\n\n    auto ref = buffer.string(m_completions.begin, m_completions.end);\n    Vector<BufferRange> ranges;\n    for (auto& sel : selections)\n    {\n        auto pos = buffer.iterator_at(sel.cursor());\n        if (pos.coord().column >= prefix_len and (pos + suffix_len) != buffer.end() and std::equal(ref.begin(), ref.end(), pos - prefix_len))\n            ranges.push_back({(pos - prefix_len).coord(), (pos + suffix_len).coord()});\n    }\n    replace(buffer, ranges, candidate.completion);\n\n    selections.update();\n    m_completions.end = cursor_pos;\n    m_completions.begin = buffer.advance(cursor_pos, -candidate.completion.length());\n    m_completions.timestamp = buffer.timestamp();\n    m_inserted_ranges = std::move(ranges);\n\n    if (m_context.has_client())\n    {\n        m_context.client().menu_select(m_current_candidate);\n    }\n\n    {\n        for (auto i = 0_byte; i < prefix_len; ++i)\n            record_key(Key::Backspace);\n        for (auto i = 0_byte; i < suffix_len; ++i)\n            record_key(Key::Delete);\n        for (auto& c : candidate.completion)\n            record_key(c);\n    }\n\n    if (not candidate.on_select.empty())\n        CommandManager::instance().execute(candidate.on_select, m_context);\n}\n\nvoid InsertCompleter::update(bool allow_implicit)\n{\n    m_enabled = allow_implicit;\n    if (m_explicit_completer and try_complete(m_explicit_completer))\n        return;\n\n    reset();\n    setup_ifn();\n}\n\nauto& get_first(BufferRange& range) { return range.begin; }\nauto& get_last(BufferRange& range) { return range.end; }\n\nbool InsertCompleter::has_candidate_selected() const\n{\n    return m_completions.is_valid() and m_current_candidate >= 0 and m_current_candidate < m_completions.candidates.size() - 1;\n}\n\nvoid InsertCompleter::try_accept()\n{\n    if (has_candidate_selected())\n        reset();\n}\n\nvoid InsertCompleter::reset()\n{\n    if (not m_explicit_completer and not m_completions.is_valid())\n        return;\n\n    String hook_param;\n    if (m_context.has_client() and has_candidate_selected())\n    {\n        auto& buffer = m_context.buffer();\n        update_ranges(buffer, m_completions.timestamp, m_inserted_ranges);\n        m_completions.timestamp = buffer.timestamp();\n\n        hook_param = join(m_inserted_ranges | filter([](auto&& r) { return not r.empty(); }) | transform([&](auto&& r) {\n                return selection_to_string(ColumnType::Byte, buffer, {r.begin, buffer.char_prev(r.end)});\n            }), ' ');\n    }\n\n    m_explicit_completer = nullptr;\n    m_completions = InsertCompletion{};\n    m_inserted_ranges.clear();\n    if (m_context.has_client())\n    {\n        m_context.client().menu_hide();\n        m_context.client().info_hide();\n        m_context.hooks().run_hook(Hook::InsertCompletionHide, hook_param, m_context);\n    }\n}\n\nbool InsertCompleter::setup_ifn()\n{\n    if (!m_enabled)\n        return false;\n    if (not m_completions.is_valid())\n    {\n        auto& completers = m_options[\"completers\"].get<InsertCompleterDescList>();\n        for (auto& completer : completers)\n        {\n            if (completer.mode == InsertCompleterDesc::Filename and\n                try_complete(complete_filename<true>))\n                return true;\n            if (completer.mode == InsertCompleterDesc::Option and\n                try_complete([&](const SelectionList& sels,\n                                 const OptionManager& options,\n                                 const FaceRegistry& faces) {\n                   return complete_option(sels, options, faces, *completer.param);\n                }))\n                return true;\n            if (completer.mode == InsertCompleterDesc::Word and\n                *completer.param == \"buffer\" and\n                try_complete(complete_word<false>))\n                return true;\n            if (completer.mode == InsertCompleterDesc::Word and\n                *completer.param == \"all\" and\n                try_complete(complete_word<true>))\n                return true;\n            if (completer.mode == InsertCompleterDesc::Line and\n                *completer.param == \"buffer\" and\n                try_complete(complete_line<false>))\n                return true;\n            if (completer.mode == InsertCompleterDesc::Line and\n                *completer.param == \"all\" and\n                try_complete(complete_line<true>))\n                return true;\n        }\n        return false;\n    }\n    return true;\n}\n\nvoid InsertCompleter::menu_show()\n{\n    if (not m_context.has_client())\n        return;\n\n    Vector<DisplayLine> menu_entries;\n    for (auto& candidate : m_completions.candidates)\n        menu_entries.push_back(candidate.menu_entry);\n\n    m_context.client().menu_show(std::move(menu_entries), m_completions.begin,\n                                 MenuStyle::Inline);\n    m_context.client().menu_select(m_current_candidate);\n    m_context.hooks().run_hook(Hook::InsertCompletionShow, \"\", m_context);\n}\n\nvoid InsertCompleter::on_option_changed(const Option& opt)\n{\n    // Do not reset the menu if the user has selected an entry\n    if (not m_completions.candidates.empty() and\n        m_current_candidate != m_completions.candidates.size() - 1)\n        return;\n\n    const auto& completers = m_options[\"completers\"].get<InsertCompleterDescList>();\n    for (auto& completer : completers)\n    {\n        if (completer.mode == InsertCompleterDesc::Option and\n            *completer.param == opt.name())\n        {\n            reset();\n            setup_ifn();\n            break;\n        }\n    }\n}\n\ntemplate<typename Func>\nbool InsertCompleter::try_complete(Func complete_func)\n{\n    auto& sels = m_context.selections();\n    try\n    {\n        reset();\n        m_completions = complete_func(sels, m_options, m_faces);\n    }\n    catch (runtime_error& e)\n    {\n        write_to_debug_buffer(format(\"error while trying to run completer: {}\", e.what()));\n        return false;\n    }\n    if (not m_completions.is_valid())\n        return false;\n\n    kak_assert(m_completions.begin <= sels.main().cursor());\n    m_current_candidate = m_completions.candidates.size();\n    menu_show();\n    m_completions.candidates.push_back({sels.buffer().string(m_completions.begin, m_completions.end), \"\", {}});\n    return true;\n}\n\nvoid InsertCompleter::explicit_file_complete()\n{\n    try_complete(complete_filename<false>);\n    m_explicit_completer = complete_filename<false>;\n}\n\nvoid InsertCompleter::explicit_word_buffer_complete()\n{\n    try_complete(complete_word<false>);\n    m_explicit_completer = complete_word<false>;\n}\n\nvoid InsertCompleter::explicit_word_all_complete()\n{\n    try_complete(complete_word<true>);\n    m_explicit_completer = complete_word<true>;\n}\n\nvoid InsertCompleter::explicit_line_buffer_complete()\n{\n    try_complete(complete_line<false>);\n    m_explicit_completer = complete_line<false>;\n}\n\nvoid InsertCompleter::explicit_line_all_complete()\n{\n    try_complete(complete_line<true>);\n    m_explicit_completer = complete_line<true>;\n}\n\n}\n"
  },
  {
    "path": "src/insert_completer.hh",
    "content": "#ifndef insert_completer_hh_INCLUDED\n#define insert_completer_hh_INCLUDED\n\n#include \"option.hh\"\n#include \"display_buffer.hh\"\n#include \"vector.hh\"\n#include \"utils.hh\"\n\n#include \"optional.hh\"\n\nnamespace Kakoune\n{\n\nstruct SelectionList;\nstruct Key;\nclass Context;\nclass OptionManager;\nclass FaceRegistry;\n\nstruct InsertCompleterDesc\n{\n    enum Mode\n    {\n        Word,\n        Option,\n        Filename,\n        Line\n    };\n\n    bool operator==(const InsertCompleterDesc& other) const = default;\n\n    Mode mode;\n    Optional<String> param;\n};\n\nusing InsertCompleterDescList = Vector<InsertCompleterDesc, MemoryDomain::Options>;\n\nString option_to_string(const InsertCompleterDesc& opt);\nInsertCompleterDesc option_from_string(Meta::Type<InsertCompleterDesc>, StringView str);\n\ninline StringView option_type_name(Meta::Type<InsertCompleterDesc>)\n{\n    return \"completer\";\n}\n\nusing CompletionCandidate = std::tuple<String, String, String>;\nusing CompletionList = PrefixedList<String, CompletionCandidate>;\n\ninline StringView option_type_name(Meta::Type<CompletionList>)\n{\n    return \"completions\";\n}\n\nstruct InsertCompletion\n{\n    struct Candidate\n    {\n        String completion;\n        String on_select;\n        DisplayLine menu_entry;\n\n        bool operator==(const Candidate& other) const { return completion == other.completion; }\n        auto operator<=>(const Candidate& other) const { return completion <=> other.completion; }\n    };\n    using CandidateList = Vector<Candidate, MemoryDomain::Completion>;\n\n    CandidateList candidates;\n    BufferCoord begin;\n    BufferCoord end;\n    size_t timestamp = 0;\n\n    bool is_valid() const { return not candidates.empty(); }\n};\n\nclass InsertCompleter : public OptionWatcher\n{\npublic:\n    InsertCompleter(Context& context);\n    InsertCompleter(const InsertCompleter&) = delete;\n    InsertCompleter& operator=(const InsertCompleter&) = delete;\n    ~InsertCompleter();\n\n    void select(int index, bool relative, FunctionRef<void (Key)> record_key);\n    void update(bool allow_implicit);\n    void try_accept();\n    void reset();\n\n    void explicit_file_complete();\n    void explicit_word_buffer_complete();\n    void explicit_word_all_complete();\n    void explicit_line_buffer_complete();\n    void explicit_line_all_complete();\n\n    bool has_candidate_selected() const;\n\nprivate:\n    bool setup_ifn();\n\n    template<typename Func>\n    bool try_complete(Func complete_func);\n    void on_option_changed(const Option& opt) override;\n\n    void menu_show();\n\n    Context&            m_context;\n    OptionManager&      m_options;\n    const FaceRegistry& m_faces;\n    InsertCompletion    m_completions;\n    Vector<BufferRange> m_inserted_ranges;\n    int                 m_current_candidate = -1;\n    bool                m_enabled = true;\n\n    using CompleteFunc = InsertCompletion (const SelectionList& sels,\n                                           const OptionManager& options,\n                                           const FaceRegistry& faces);\n    CompleteFunc* m_explicit_completer = nullptr;\n};\n\n}\n\n#endif // insert_completer_hh_INCLUDED\n"
  },
  {
    "path": "src/json.cc",
    "content": "#include \"json.hh\"\n\n#include \"exception.hh\"\n#include \"string_utils.hh\"\n#include \"unit_tests.hh\"\n#include \"utils.hh\"\n#include \"ranges.hh\"\n\n#include <algorithm>\n#include <cstdio>\n\nnamespace Kakoune\n{\n\nString to_json(int i) { return to_string(i); }\nString to_json(bool b) { return b ? \"true\" : \"false\"; }\nString to_json(StringView str)\n{\n    String res;\n    res.reserve(str.length() + 4);\n    res += '\"';\n    for (auto it = str.begin(), end = str.end(); it != end; )\n    {\n        auto next = std::find_if(it, end, [](char c) {\n            return c == '\\\\' or c == '\"' or (unsigned char) c <= 0x1F;\n        });\n\n        res += StringView{it, next};\n        if (next == end)\n            break;\n\n        char buf[7] = {'\\\\', *next, 0};\n        if ((unsigned char) *next <= 0x1F)\n            format_to(buf, \"\\\\u{:04}\", hex(*next));\n\n        res += buf;\n        it = next+1;\n    }\n    res += '\"';\n    return res;\n}\n\nstatic bool is_digit(char c) { return c >= '0' and c <= '9'; }\n\nstatic constexpr size_t max_parsing_depth = 100;\n\nJsonResult parse_json_impl(const char* pos, const char* end, size_t depth)\n{\n    if (not skip_while(pos, end, is_blank))\n        return {};\n\n    if (depth >= max_parsing_depth)\n        throw runtime_error(\"maximum parsing depth reached\");\n\n    if (is_digit(*pos) or *pos == '-')\n    {\n        auto digit_end = pos + 1;\n        skip_while(digit_end, end, is_digit);\n        return { Value{str_to_int({pos, digit_end})}, digit_end };\n    }\n    if (end - pos > 4 and StringView{pos, pos+4} == \"true\")\n        return { Value{true}, pos+4 };\n    if (end - pos > 5 and StringView{pos, pos+5} == \"false\")\n        return { Value{false}, pos+5 };\n    if (*pos == '\"')\n    {\n        String value;\n        bool escaped = false;\n        ++pos;\n        for (auto string_end = pos; string_end != end; ++string_end)\n        {\n            if (escaped)\n            {\n                escaped = false;\n                value += StringView{pos, string_end};\n                value.back() = *string_end;\n                pos = string_end+1;\n                continue;\n            }\n            if (*string_end == '\\\\')\n                escaped = true;\n            if (*string_end == '\"')\n            {\n                value += StringView{pos, string_end};\n                return {std::move(value), string_end+1};\n            }\n        }\n        return {};\n    }\n    if (*pos == '[')\n    {\n        JsonArray array;\n        if (++pos == end)\n            throw runtime_error(\"unable to parse array\");\n        if (*pos == ']')\n            return {std::move(array), pos+1};\n\n        while (true)\n        {\n            auto [element, new_pos] = parse_json_impl(pos, end, depth+1);\n            if (not element)\n                return {};\n            pos = new_pos;\n            array.push_back(std::move(element));\n            if (not skip_while(pos, end, is_blank))\n                return {};\n\n            if (*pos == ',')\n                ++pos;\n            else if (*pos == ']')\n                return {std::move(array), pos+1};\n            else\n                throw runtime_error(\"unable to parse array, expected ',' or ']'\");\n        }\n    }\n    if (*pos == '{')\n    {\n        if (++pos == end)\n            throw runtime_error(\"unable to parse object\");\n        JsonObject object;\n        if (*pos == '}')\n            return {std::move(object), pos+1};\n\n        while (true)\n        {\n            auto [name_value, name_end] = parse_json_impl(pos, end, depth+1);\n            if (not name_value)\n                return {};\n            pos = name_end;\n            String& name = name_value.as<String>();\n            if (not skip_while(pos, end, is_blank))\n                return {};\n            if (*pos++ != ':')\n                throw runtime_error(\"expected :\");\n\n            auto [element, element_end] = parse_json_impl(pos, end, depth+1);\n            if (not element)\n                return {};\n            pos = element_end;\n            object.insert({ std::move(name), std::move(element) });\n            if (not skip_while(pos, end, is_blank))\n                return {};\n\n            if (*pos == ',')\n                ++pos;\n            else if (*pos == '}')\n                return {std::move(object), pos+1};\n            else\n                throw runtime_error(\"unable to parse object, expected ',' or '}'\");\n        }\n    }\n    throw runtime_error(\"unable to parse json\");\n}\n\nJsonResult parse_json(const char* pos, const char* end) { return parse_json_impl(pos,          end,        0); }\nJsonResult parse_json(StringView json)                  { return parse_json_impl(json.begin(), json.end(), 0); }\n\nUnitTest test_json_parser{[]()\n{\n    {\n        auto value = parse_json(R\"({ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"b\", \"l\", \"a\", \"h\" ] })\").value;\n        kak_assert(value);\n    }\n\n    {\n        auto value = parse_json(\"[10,20]\").value;\n        kak_assert(value and value.is_a<JsonArray>());\n        kak_assert(value.as<JsonArray>().at(1).as<int>() == 20);\n    }\n\n    {\n        auto value = parse_json(\"-1\").value;\n        kak_assert(value.as<int>() == -1);\n    }\n\n    {\n        auto value = parse_json(\"{}\").value;\n        kak_assert(value and value.is_a<JsonObject>());\n        kak_assert(value.as<JsonObject>().empty());\n    }\n\n    {\n        char big_nested_array[max_parsing_depth*2+2+1] = {};\n        for (size_t i = 0; i < max_parsing_depth+1; i++)\n        {\n            big_nested_array[i] = '[';\n            big_nested_array[i+max_parsing_depth+1] = ']';\n        }\n        kak_expect_throw(runtime_error, parse_json(big_nested_array));\n    }\n}};\n\nUnitTest test_to_json{[]()\n{\n    kak_assert(to_json(true) == \"true\");\n    kak_assert(to_json(false) == \"false\");\n    kak_assert(to_json(HashMap<String, Vector<int>>{{\"foo\", {1,2,3}}, {\"\\033\", {3, 4, 5}}}) == R\"({\"foo\": [1, 2, 3],\"\\u001b\": [3, 4, 5]})\");\n}};\n\n}\n"
  },
  {
    "path": "src/json.hh",
    "content": "#ifndef json_hh_INCLUDED\n#define json_hh_INCLUDED\n\n#include \"hash_map.hh\"\n#include \"string.hh\"\n#include \"string_utils.hh\"\n#include \"value.hh\"\n\nnamespace Kakoune\n{\n\nusing JsonArray = Vector<Value>;\nusing JsonObject = HashMap<String, Value>;\n\nString to_json(int i);\nString to_json(bool b);\nString to_json(StringView str);\n\ntemplate<typename T>\nString to_json(ArrayView<const T> array)\n{\n    return \"[\" + join(array | transform([](auto&& elem) { return to_json(elem); }), \", \") + \"]\";\n}\n\ntemplate<typename T, MemoryDomain D>\nString to_json(const Vector<T, D>& vec) { return to_json(ArrayView<const T>{vec}); }\n\ntemplate<typename K, typename V, MemoryDomain D>\nString to_json(const HashMap<K, V, D>& map)\n{\n    return \"{\" + join(map | transform([](auto&& i) { return format(\"{}: {}\", to_json(i.key), to_json(i.value)); }),\n                      ',', false) + \"}\";\n}\n\nstruct JsonResult { Value value; const char* new_pos; };\n\nJsonResult parse_json(const char* pos, const char* end);\nJsonResult parse_json(StringView json);\n\n}\n\n#endif // json_hh_INCLUDED\n"
  },
  {
    "path": "src/json_ui.cc",
    "content": "#include \"json_ui.hh\"\n\n#include \"display_buffer.hh\"\n#include \"event_manager.hh\"\n#include \"exception.hh\"\n#include \"file.hh\"\n#include \"json.hh\"\n#include \"keys.hh\"\n#include \"ranges.hh\"\n#include \"string_utils.hh\"\n#include \"format.hh\"\n\n#include <cstdio>\n#include <utility>\n\n#include <unistd.h>\n\nnamespace Kakoune\n{\n\nstruct invalid_rpc_request : runtime_error {\n    invalid_rpc_request(const String& message)\n        : runtime_error(format(\"invalid json rpc request ({})\", message)) {}\n};\n\nString to_json(Color color)\n{\n    if (color.color == Kakoune::Color::RGB)\n    {\n        char buffer[10];\n        format_to(buffer, R\"(\"#{:02}{:02}{:02}\")\", hex(color.r), hex(color.g), hex(color.b));\n        return buffer;\n    }\n    return to_json(to_string(color));\n}\n\nString to_json(Attribute attributes)\n{\n    struct Attr { Attribute attr; StringView name; }\n    attrs[] {\n        { Attribute::Underline, \"underline\" },\n        { Attribute::CurlyUnderline, \"curly_underline\" },\n        { Attribute::DoubleUnderline, \"double_underline\" },\n        { Attribute::Reverse, \"reverse\" },\n        { Attribute::Blink, \"blink\" },\n        { Attribute::Bold, \"bold\" },\n        { Attribute::Dim, \"dim\" },\n        { Attribute::Italic, \"italic\" },\n        { Attribute::FinalFg, \"final_fg\" },\n        { Attribute::FinalBg, \"final_bg\" },\n        { Attribute::FinalAttr, \"final_attr\" },\n        { Attribute::Strikethrough, \"strikethrough\" },\n    };\n\n    return \"[\" + join(attrs |\n                      filter([=](const Attr& a) { return attributes & a.attr; }) |\n                      transform([](const Attr& a) { return to_json(a.name); }),\n                      ',', false) + \"]\";\n}\n\nString to_json(Face face)\n{\n    return format(R\"(\\{ \"fg\": {}, \"bg\": {}, \"underline\": {}, \"attributes\": {} })\",\n                  to_json(face.fg), to_json(face.bg), to_json(face.underline), to_json(face.attributes));\n}\n\nString to_json(const DisplayAtom& atom)\n{\n    return format(R\"(\\{ \"face\": {}, \"contents\": {} })\", to_json(atom.face), to_json(atom.content()));\n}\n\nString to_json(const DisplayLine& line)\n{\n    return to_json(line.atoms());\n}\n\nString to_json(ColumnCount column)\n{\n    return format(\"{}\", column);\n}\n\nString to_json(DisplayCoord coord)\n{\n    return format(R\"(\\{ \"line\": {}, \"column\": {} })\", coord.line, coord.column);\n}\n\nString to_json(MenuStyle style)\n{\n    switch (style)\n    {\n        case MenuStyle::Prompt: return R\"(\"prompt\")\";\n        case MenuStyle::Search: return R\"(\"search\")\";\n        case MenuStyle::Inline: return R\"(\"inline\")\";\n    }\n    return \"\";\n}\n\nString to_json(InfoStyle style)\n{\n    switch (style)\n    {\n        case InfoStyle::Prompt: return R\"(\"prompt\")\";\n        case InfoStyle::Inline: return R\"(\"inline\")\";\n        case InfoStyle::InlineAbove: return R\"(\"inlineAbove\")\";\n        case InfoStyle::InlineBelow: return R\"(\"inlineBelow\")\";\n        case InfoStyle::MenuDoc: return R\"(\"menuDoc\")\";\n        case InfoStyle::Modal: return R\"(\"modal\")\";\n    }\n    return \"\";\n}\n\nString concat()\n{\n    return \"\";\n}\n\ntemplate<typename First, typename... Args>\nString concat(First&& first, Args&&... args)\n{\n    if (sizeof...(Args) != 0)\n        return to_json(first) + \", \" + concat(args...);\n    return to_json(first);\n}\n\ntemplate<typename... Args>\nvoid rpc_call(StringView method, Args&&... args)\n{\n    auto q = format(R\"(\\{ \"jsonrpc\": \"2.0\", \"method\": \"{}\", \"params\": [{}] }{})\",\n                    method, concat(std::forward<Args>(args)...), \"\\n\");\n\n    write(1, q);\n}\n\nJsonUI::JsonUI()\n    : m_stdin_watcher{0, FdEvents::Read, EventMode::Urgent,\n                      [this](FDWatcher&, FdEvents, EventMode mode) {\n        parse_requests(mode);\n      }}, m_dimensions{24, 80}\n{\n    set_signal_handler(SIGINT, SIG_DFL);\n}\n\nvoid JsonUI::draw(const DisplayBuffer& display_buffer, DisplayCoord cursor_pos,\n                  const Face& default_face, const Face& padding_face,\n                  ColumnCount widget_columns)\n{\n    rpc_call(\"draw\", display_buffer.lines(), cursor_pos, default_face, padding_face,\n             widget_columns);\n}\n\nvoid JsonUI::draw_status(const DisplayLine& prompt,\n                         const DisplayLine& content,\n                         const ColumnCount cursor_pos,\n                         const DisplayLine& mode_line,\n                         const Face& default_face)\n{\n    rpc_call(\"draw_status\", prompt, content, cursor_pos, mode_line, default_face);\n}\n\n\nvoid JsonUI::menu_show(ConstArrayView<DisplayLine> items,\n                       DisplayCoord anchor, Face fg, Face bg,\n                       MenuStyle style)\n{\n    rpc_call(\"menu_show\", items, anchor, fg, bg, style);\n}\n\nvoid JsonUI::menu_select(int selected)\n{\n    rpc_call(\"menu_select\", selected);\n}\n\nvoid JsonUI::menu_hide()\n{\n    rpc_call(\"menu_hide\");\n}\n\nvoid JsonUI::info_show(const DisplayLine& title, const DisplayLineList& content,\n                       DisplayCoord anchor, Face face,\n                       InfoStyle style)\n{\n    rpc_call(\"info_show\", title, content, anchor, face, style);\n}\n\nvoid JsonUI::info_hide()\n{\n    rpc_call(\"info_hide\");\n}\n\nvoid JsonUI::refresh(bool force)\n{\n    rpc_call(\"refresh\", force);\n}\n\nvoid JsonUI::set_ui_options(const Options& options)\n{\n    rpc_call(\"set_ui_options\", options);\n}\n\nDisplayCoord JsonUI::dimensions()\n{\n    return m_dimensions;\n}\n\nvoid JsonUI::set_on_key(OnKeyCallback callback)\n{\n    m_on_key = std::move(callback);\n}\n\nvoid JsonUI::set_on_paste(OnPasteCallback callback)\n{\n    m_on_paste = std::move(callback);\n}\n\nvoid JsonUI::eval_json(const Value& json)\n{\n    if (not json.is_a<JsonObject>())\n        throw invalid_rpc_request(\"request is not an object\");\n\n    const JsonObject& object = json.as<JsonObject>();\n    auto json_it = object.find(\"jsonrpc\"_sv);\n    if (json_it == object.end() or\n        not json_it->value.is_a<String>() or\n        json_it->value.as<String>() != \"2.0\")\n        throw invalid_rpc_request(\"only protocol '2.0' is supported\");\n    else if (not json_it->value.is_a<String>())\n        throw invalid_rpc_request(\"'jsonrpc' is not a string\");\n\n    auto method_it = object.find(\"method\"_sv);\n    if (method_it == object.end())\n        throw invalid_rpc_request(\"method missing\");\n    else if (not method_it->value.is_a<String>())\n        throw invalid_rpc_request(\"'method' is not a string\");\n    StringView method = method_it->value.as<String>();\n\n    auto params_it = object.find(\"params\"_sv);\n    if (params_it == object.end())\n        throw invalid_rpc_request(\"params missing\");\n    else if (not params_it->value.is_a<JsonArray>())\n        throw invalid_rpc_request(\"'params' is not an array\");\n    const JsonArray& params = params_it->value.as<JsonArray>();\n\n    if (method == \"keys\")\n    {\n        for (auto& key_val : params)\n        {\n            if (not key_val.is_a<String>())\n                throw invalid_rpc_request(\"'keys' is not an array of strings\");\n\n            for (auto& key : parse_keys(key_val.as<String>()))\n                m_on_key(key);\n        }\n    }\n    else if (method == \"mouse_move\")\n    {\n        if (params.size() != 2)\n            throw invalid_rpc_request(\"mouse coordinates not specified\");\n\n        if (not params[0].is_a<int>() or not params[1].is_a<int>())\n            throw invalid_rpc_request(\"mouse coordinates are not integers\");\n\n        m_on_key({Key::Modifiers::MousePos, encode_coord({params[0].as<int>(), params[1].as<int>()})});\n    }\n    else if (method == \"mouse_press\" or method == \"mouse_release\")\n    {\n        if (params.size() != 3)\n            throw invalid_rpc_request(\"mouse button/coordinates not specified\");\n\n        if (not params[0].is_a<String>())\n            throw invalid_rpc_request(\"mouse button is not a string\");\n        if (not params[1].is_a<int>() or not params[2].is_a<int>())\n            throw invalid_rpc_request(\"mouse coordinates are not integers\");\n\n        auto event = method == \"mouse_press\" ? Key::Modifiers::MousePress : Key::Modifiers::MouseRelease;\n        auto button = str_to_button(params[0].as<String>());\n\n        m_on_key({event | Key::to_modifier(button), encode_coord({params[1].as<int>(), params[2].as<int>()})});\n    }\n    else if (method == \"scroll\")\n    {\n        if (params.size() != 3)\n            throw invalid_rpc_request(\"scroll needs an amount and coordinates\");\n        else if (not params[0].is_a<int>() or not params[1].is_a<int>() or not params[2].is_a<int>())\n            throw invalid_rpc_request(\"scroll parameters are not integers\");\n        m_on_key({Key::Modifiers::Scroll | (Key::Modifiers)(params[0].as<int>() << 16),\n                  encode_coord({params[1].as<int>(), params[2].as<int>()})});\n    }\n    else if (method == \"menu_select\")\n    {\n        if (params.size() != 1)\n            throw invalid_rpc_request(\"menu_select needs the item index\");\n        else if (not params[0].is_a<int>())\n            throw invalid_rpc_request(\"menu index is not an integer\");\n\n        m_on_key({Key::Modifiers::MenuSelect, (Codepoint)params[0].as<int>()});\n    }\n    else if (method == \"resize\")\n    {\n        if (params.size() != 2)\n            throw runtime_error(\"resize expects 2 parameters\");\n        else if (not params[0].is_a<int>() or\n                 not params[1].is_a<int>())\n            throw invalid_rpc_request(\"width and height are not integers\");\n\n        DisplayCoord dim{params[0].as<int>(), params[1].as<int>()};\n        m_dimensions = dim;\n        m_on_key(resize(dim));\n    }\n    else\n        throw invalid_rpc_request(format(\"unknown method: {}\", method));\n}\n\nvoid JsonUI::parse_requests(EventMode mode)\n{\n    constexpr size_t bufsize = 1024;\n    char buf[bufsize];\n    while (fd_readable(0))\n    {\n        ssize_t size = ::read(0, buf, bufsize);\n        if (size == -1 or size == 0)\n        {\n            m_stdin_watcher.close_fd();\n            break;\n        }\n\n        m_requests += StringView{buf, buf + size};\n    }\n\n    if (not m_on_key)\n        return;\n\n    while (not m_requests.empty())\n    {\n        const char* pos = nullptr;\n        try\n        {\n            auto [json, new_pos] = parse_json(m_requests);\n            pos = new_pos;\n            if (json)\n                eval_json(json);\n        }\n        catch (runtime_error& error)\n        {\n            write(2, format(\"error while handling requests '{}': '{}'\\n\",\n                            m_requests, error.what()));\n            // try to salvage request by dropping its first line\n            pos = std::min(m_requests.end(), find(m_requests, '\\n')+1);\n        }\n        if (not pos)\n            break; // unterminated request ?\n\n        m_requests = String{pos, m_requests.end()};\n    }\n}\n\n}\n"
  },
  {
    "path": "src/json_ui.hh",
    "content": "#ifndef json_ui_hh_INCLUDED\n#define json_ui_hh_INCLUDED\n\n#include \"user_interface.hh\"\n#include \"event_manager.hh\"\n#include \"coord.hh\"\n#include \"string.hh\"\n\nnamespace Kakoune\n{\n\nstruct Value;\n\nclass JsonUI : public UserInterface\n{\npublic:\n    JsonUI();\n\n    JsonUI(const JsonUI&) = delete;\n    JsonUI& operator=(const JsonUI&) = delete;\n\n    bool is_ok() const override { return m_stdin_watcher.fd() != -1; }\n\n    void draw(const DisplayBuffer& display_buffer,\n              DisplayCoord cursor_pos,\n              const Face& default_face,\n              const Face& buffer_padding,\n              ColumnCount widget_columns) override;\n\n    void draw_status(const DisplayLine& prompt,\n                     const DisplayLine& content,\n                     const ColumnCount cursor_pos,\n                     const DisplayLine& mode_line,\n                     const Face& default_face) override;\n\n    void menu_show(ConstArrayView<DisplayLine> items,\n                   DisplayCoord anchor, Face fg, Face bg,\n                   MenuStyle style) override;\n    void menu_select(int selected) override;\n    void menu_hide() override;\n\n    void info_show(const DisplayLine& title, const DisplayLineList& content,\n                   DisplayCoord anchor, Face face,\n                   InfoStyle style) override;\n    void info_hide() override;\n\n    void refresh(bool force) override;\n\n    DisplayCoord dimensions() override;\n    void set_on_key(OnKeyCallback callback) override;\n    void set_on_paste(OnPasteCallback callback) override;\n    void set_ui_options(const Options& options) override;\n\nprivate:\n    void parse_requests(EventMode mode);\n    void eval_json(const Value& value);\n\n    FDWatcher m_stdin_watcher;\n    OnKeyCallback m_on_key;\n    OnPasteCallback m_on_paste;\n    Vector<Key, MemoryDomain::Client> m_pending_keys;\n    DisplayCoord m_dimensions;\n    String m_requests;\n};\n\n}\n\n#endif // json_ui_hh_INCLUDED\n"
  },
  {
    "path": "src/keymap_manager.cc",
    "content": "#include \"keymap_manager.hh\"\n\n#include \"assert.hh\"\n#include \"exception.hh\"\n#include \"format.hh\"\n#include \"ranges.hh\"\n\nnamespace Kakoune\n{\n\nvoid KeymapManager::map_key(Key key, KeymapMode mode,\n                            KeyList mapping, String docstring)\n{\n    m_mapping[KeyAndMode{key, mode}] = {std::move(mapping), std::move(docstring)};\n}\n\nvoid KeymapManager::unmap_key(Key key, KeymapMode mode)\n{\n    m_mapping.remove(KeyAndMode{key, mode});\n}\n\nvoid KeymapManager::unmap_keys(KeymapMode mode)\n{\n    auto it = m_mapping.begin();\n    while (it != m_mapping.end())\n    {\n        auto& map = *it;\n        if (map.key.second == mode)\n            unmap_key(map.key.first, map.key.second);\n        else\n            ++it;\n    }\n}\n\nbool KeymapManager::is_mapped(Key key, KeymapMode mode) const\n{\n    return m_mapping.find(KeyAndMode{key, mode}) != m_mapping.end() or\n           (m_parent and m_parent->is_mapped(key, mode));\n}\n\nconst KeymapManager::KeymapInfo&\nKeymapManager::get_mapping(Key key, KeymapMode mode) const\n{\n    auto it = m_mapping.find(KeyAndMode{key, mode});\n    if (it != m_mapping.end())\n        return it->value;\n    kak_assert(m_parent);\n    return m_parent->get_mapping(key, mode);\n}\n\nKeymapManager::KeyList KeymapManager::get_mapped_keys(KeymapMode mode) const\n{\n    KeyList res;\n    if (m_parent)\n        res = m_parent->get_mapped_keys(mode);\n    for (auto& map : m_mapping)\n    {\n        if (map.key.second == mode and not contains(res, map.key.first))\n            res.emplace_back(map.key.first);\n    }\n    return res;\n}\n\nvoid KeymapManager::add_user_mode(String user_mode_name)\n{\n    auto modes = {\"normal\", \"insert\", \"prompt\", \"menu\", \"goto\", \"view\", \"user\", \"object\"};\n\n    if (contains(modes, user_mode_name))\n        throw runtime_error(format(\"'{}' is already a regular mode\", user_mode_name));\n\n    if (contains(user_modes(), user_mode_name))\n        throw runtime_error(format(\"user mode '{}' already defined\", user_mode_name));\n\n    if (not all_of(user_mode_name, is_identifier))\n        throw runtime_error(format(\"invalid mode name: '{}'\", user_mode_name));\n\n    user_modes().push_back(std::move(user_mode_name));\n}\n\n}\n"
  },
  {
    "path": "src/keymap_manager.hh",
    "content": "#ifndef keymap_manager_hh_INCLUDED\n#define keymap_manager_hh_INCLUDED\n\n#include \"keys.hh\"\n#include \"string.hh\"\n#include \"hash_map.hh\"\n#include \"vector.hh\"\n\nnamespace Kakoune\n{\n\nenum class KeymapMode : char\n{\n    None,\n    Normal,\n    Insert,\n    Prompt,\n    Menu,\n    Goto,\n    View,\n    User,\n    Object,\n    FirstUserMode,\n};\n\nclass KeymapManager\n{\npublic:\n    KeymapManager(KeymapManager& parent) : m_parent(&parent) {}\n\n    void reparent(KeymapManager& parent) { m_parent = &parent; }\n\n    using KeyList = Vector<Key, MemoryDomain::Mapping>;\n    void map_key(Key key, KeymapMode mode, KeyList mapping, String docstring);\n    void unmap_key(Key key, KeymapMode mode);\n    void unmap_keys(KeymapMode mode);\n\n    bool is_mapped(Key key, KeymapMode mode) const;\n    KeyList get_mapped_keys(KeymapMode mode) const;\n\n    auto get_mapping_keys(Key key, KeymapMode mode) {\n        return get_mapping(key, mode).keys;\n    }\n\n    const String& get_mapping_docstring(Key key, KeymapMode mode) { return get_mapping(key, mode).docstring; }\n\n    using UserModeList = Vector<String>;\n    UserModeList& user_modes() {\n        if (m_parent)\n            return m_parent->user_modes();\n        return m_user_modes;\n    }\n    void add_user_mode(String user_mode_name);\n\nprivate:\n    struct KeymapInfo\n    {\n        KeyList keys;\n        String docstring;\n    };\n    const KeymapInfo& get_mapping(Key key, KeymapMode mode) const;\n\n    KeymapManager()\n        : m_parent(nullptr) {}\n    // the only one allowed to construct a root map manager\n    friend class Scope;\n\n    KeymapManager* m_parent;\n    using KeyAndMode = std::pair<Key, KeymapMode>;\n    HashMap<KeyAndMode, KeymapInfo, MemoryDomain::Mapping> m_mapping;\n\n    UserModeList m_user_modes;\n};\n\n}\n\n#endif // keymap_manager_hh_INCLUDED\n"
  },
  {
    "path": "src/keys.cc",
    "content": "#include \"keys.hh\"\n\n#include \"exception.hh\"\n#include \"ranges.hh\"\n#include \"string.hh\"\n#include \"unit_tests.hh\"\n#include \"utf8_iterator.hh\"\n#include \"format.hh\"\n#include \"string_utils.hh\"\n\nnamespace Kakoune\n{\n\nstruct key_parse_error : runtime_error\n{\n    using runtime_error::runtime_error;\n};\n\nstatic Key canonicalize_ifn(Key key)\n{\n    if (key.key > 0 and key.key < 27)\n    {\n        kak_assert(key.modifiers == Key::Modifiers::None);\n        key.modifiers = Key::Modifiers::Control;\n        key.key = key.key - 1 + 'a';\n    }\n\n    if (key.modifiers & Key::Modifiers::Shift)\n    {\n        if (is_basic_alpha(key.key))\n        {\n            // Shift + ASCII letters is just the uppercase letter.\n            key.modifiers &= ~Key::Modifiers::Shift;\n            key.key = to_upper(key.key);\n        }\n        else if (key.key < 0xD800 || key.key > 0xDFFF)\n        {\n            // Shift + any other printable character is not allowed.\n            throw key_parse_error(format(\"Shift modifier only works on special keys and lowercase ASCII, not '{}'\", key.key));\n        }\n    }\n\n    return key;\n}\n\nOptional<Codepoint> Key::codepoint() const\n{\n    if (*this == Key::Return)\n        return '\\n';\n    if (*this == Key::Tab)\n        return '\\t';\n    if (*this == Key::Space or *this == shift(Key::Space))\n        return ' ';\n    if (*this == Key::Escape)\n        return 0x1B;\n    if (modifiers == Modifiers::None and key > 27 and\n        (key < 0xD800 or key > 0xDFFF)) // avoid surrogates\n        return key;\n    return {};\n}\n\nstruct KeyAndName { const char* name; Codepoint key; };\nstatic constexpr KeyAndName keynamemap[] = {\n    { \"ret\", Key::Return },\n    { \"space\", Key::Space },\n    { \"tab\", Key::Tab },\n    { \"lt\", '<' },\n    { \"gt\", '>' },\n    { \"backspace\", Key::Backspace},\n    { \"esc\", Key::Escape },\n    { \"up\", Key::Up },\n    { \"down\", Key::Down},\n    { \"left\", Key::Left },\n    { \"right\", Key::Right },\n    { \"pageup\", Key::PageUp },\n    { \"pagedown\", Key::PageDown },\n    { \"home\", Key::Home },\n    { \"end\", Key::End },\n    { \"ins\", Key::Insert },\n    { \"del\", Key::Delete },\n    { \"plus\", '+' },\n    { \"minus\", '-' },\n    { \"semicolon\", ';' },\n    { \"percent\", '%' },\n    { \"quote\", '\\'' },\n    { \"dquote\", '\"' },\n    { \"focus_in\", Key::FocusIn },\n    { \"focus_out\", Key::FocusOut },\n};\n\nKeyList parse_keys(StringView str)\n{\n    KeyList result;\n    using Utf8It = utf8::iterator<const char*>;\n    for (Utf8It it{str.begin(), str}, str_end{str.end(), str}; it < str_end; ++it)\n    {\n        if (*it != '<')\n        {\n            auto convert = [](Codepoint cp) -> Codepoint {\n                switch (cp)\n                {\n                    case '\\n':   return Key::Return;\n                    case '\\r':   return Key::Return;\n                    case '\\b':   return Key::Backspace;\n                    case '\\t':   return Key::Tab;\n                    case ' ':    return Key::Space;\n                    case '\\033': return Key::Escape;\n                    default:     return cp;\n                }\n            };\n            result.emplace_back(Key::Modifiers::None, convert(*it));\n            continue;\n        }\n\n        Utf8It end_it = std::find(it, str_end, '>');\n        if (end_it == str_end)\n        {\n            result.emplace_back(Key::Modifiers::None, *it);\n            continue;\n        }\n\n        Key::Modifiers modifier = Key::Modifiers::None;\n\n        StringView full_desc{it.base(), end_it.base()+1};\n        StringView desc{it.base()+1, end_it.base()};\n        for (auto dash = find(desc, '-'); dash != desc.end(); dash = find(desc, '-'))\n        {\n            if (dash != desc.begin() + 1)\n                throw key_parse_error(format(\"unable to parse modifier in '{}'\",\n                                                    full_desc));\n\n            switch(to_lower(desc[0_byte]))\n            {\n                case 'c': modifier |= Key::Modifiers::Control; break;\n                case 'a': modifier |= Key::Modifiers::Alt; break;\n                case 's': modifier |= Key::Modifiers::Shift; break;\n                default:\n                    throw key_parse_error(format(\"unable to parse modifier in '{}'\",\n                                                        full_desc));\n            }\n            desc = StringView{dash+1, desc.end()};\n        }\n\n        auto name_it = find_if(keynamemap, [&desc](const KeyAndName& item)\n                                           { return item.name == desc; });\n        if (name_it != std::end(keynamemap))\n            result.push_back(canonicalize_ifn({ modifier, name_it->key }));\n        else if (desc.char_length() == 1)\n            result.push_back(canonicalize_ifn({ modifier, desc[0_char] }));\n        else if (desc[0_byte] == 'F' and desc.length() <= 3)\n        {\n            int val = str_to_int(desc.substr(1_byte));\n            if (val >= 1 and val <= 12)\n                result.emplace_back(modifier, Key::F1 + (val - 1));\n            else\n                throw key_parse_error(format(\"only F1 through F12 are supported, not '{}'\", desc));\n        }\n        else\n            throw key_parse_error(\"unable to parse \" +\n                                 StringView{it.base(), end_it.base()+1});\n\n        it = end_it;\n    }\n    return result;\n}\n\nStringView to_string(Key::MouseButton button)\n{\n    switch (button)\n    {\n        case Key::MouseButton::Left: return \"left\";\n        case Key::MouseButton::Middle: return \"middle\";\n        case Key::MouseButton::Right: return \"right\";\n        default: kak_assert(false); throw logic_error{};\n    }\n}\n\nKey::MouseButton str_to_button(StringView str)\n{\n    if (str == \"left\") return Key::MouseButton::Left;\n    if (str == \"middle\") return Key::MouseButton::Middle;\n    if (str == \"right\") return Key::MouseButton::Right;\n    throw runtime_error(format(\"invalid mouse button name {}\", str));\n}\n\nString to_string(Key key)\n{\n    const auto coord = key.coord() + DisplayCoord{1,1};\n    bool named = true;\n    String res;\n\n    if (key.modifiers & Key::Modifiers::MousePos)\n        res = format(\"mouse:move:{}.{}\", coord.line, coord.column);\n    else if (key.modifiers & Key::Modifiers::MousePress)\n        res = format(\"mouse:press:{}:{}.{}\", key.mouse_button(), coord.line, coord.column);\n    else if (key.modifiers & Key::Modifiers::MouseRelease)\n        res = format(\"mouse:release:{}:{}.{}\", key.mouse_button(), coord.line, coord.column);\n    else if (key.modifiers & Key::Modifiers::Scroll)\n        res = format(\"scroll:{}:{}.{}\", key.scroll_amount(), coord.line, coord.column);\n    else if (key.modifiers & Key::Modifiers::Resize)\n        res = format(\"resize:{}.{}\", coord.line, coord.column);\n    else\n    {\n        auto it = find_if(keynamemap, [&key](const KeyAndName& item)\n                                      { return item.key == key.key; });\n        if (it != std::end(keynamemap))\n            res = it->name;\n        else if (key.key >= Key::F1 and key.key <= Key::F12)\n            res = \"F\" + to_string((int)(key.key - Key::F1 + 1));\n        else\n        {\n            named = false;\n            res = String{key.key};\n        }\n    }\n\n    if (key.modifiers & Key::Modifiers::Shift)   { res = \"s-\" + res; named = true; }\n    if (key.modifiers & Key::Modifiers::Alt)     { res = \"a-\" + res; named = true; }\n    if (key.modifiers & Key::Modifiers::Control) { res = \"c-\" + res; named = true; }\n\n    if (named)\n        res = StringView{'<'} + res + StringView{'>'};\n    return res;\n}\n\nUnitTest test_keys{[]()\n{\n    KeyList keys{\n         {Key::Space},\n         { 'c' },\n         {Key::Up},\n         alt('j'),\n         ctrl('r'),\n         shift(Key::Up),\n         ctrl('['),\n         ctrl('\\\\'),\n         ctrl(']'),\n         ctrl('_'),\n    };\n    String keys_as_str;\n    for (auto& key : keys)\n        keys_as_str += to_string(key);\n    auto parsed_keys = parse_keys(keys_as_str);\n    kak_assert(keys == parsed_keys);\n    kak_assert(ConstArrayView<Key>{parse_keys(\"a<c-a-b>c\")} ==\n               ConstArrayView<Key>{'a', ctrl(alt({'b'})), 'c'});\n\n    kak_assert(parse_keys(\"x\") == KeyList{ {'x'} });\n    kak_assert(parse_keys(\"<x>\") == KeyList{ {'x'} });\n    kak_assert(parse_keys(\"<s-x>\") == KeyList{ {'X'} });\n    kak_assert(parse_keys(\"<s-X>\") == KeyList{ {'X'} });\n    kak_assert(parse_keys(\"<X>\") == KeyList{ {'X'} });\n    kak_assert(parse_keys(\"X\") == KeyList{ {'X'} });\n    kak_assert(parse_keys(\"<s-up>\") == KeyList{ shift({Key::Up}) });\n    kak_assert(parse_keys(\"<s-tab>\") == KeyList{ shift({Key::Tab}) });\n    kak_assert(parse_keys(\"\\n\") == KeyList{ Key::Return });\n\n    kak_assert(to_string(shift({Key::Tab})) == \"<s-tab>\");\n\n    kak_expect_throw(key_parse_error, parse_keys(\"<-x>\"));\n    kak_expect_throw(key_parse_error, parse_keys(\"<xy-z>\"));\n    kak_expect_throw(key_parse_error, parse_keys(\"<x-y>\"));\n    kak_expect_throw(key_parse_error, parse_keys(\"<s-/>\"));\n    kak_expect_throw(key_parse_error, parse_keys(\"<s-ë>\"));\n    kak_expect_throw(key_parse_error, parse_keys(\"<s-lt>\"));\n    kak_expect_throw(key_parse_error, parse_keys(\"<f99>\"));\n    kak_expect_throw(key_parse_error, parse_keys(\"<backtab>\"));\n    kak_expect_throw(key_parse_error, parse_keys(\"<invalidkey>\"));\n}};\n\n}\n"
  },
  {
    "path": "src/keys.hh",
    "content": "#ifndef keys_hh_INCLUDED\n#define keys_hh_INCLUDED\n\n#include \"coord.hh\"\n#include \"flags.hh\"\n#include \"hash.hh\"\n#include \"meta.hh\"\n#include \"optional.hh\"\n#include \"unicode.hh\"\n#include \"vector.hh\"\n\n#include <cstdint>\n\nnamespace Kakoune\n{\n\nstruct Key\n{\n    enum class MouseButton\n    {\n        Left,\n        Middle,\n        Right\n    };\n    enum class Modifiers : int\n    {\n        None    = 0,\n        Control = 1 << 0,\n        Alt     = 1 << 1,\n        Shift   = 1 << 2,\n\n        MousePress     = 1 << 3,\n        MouseRelease   = 1 << 4,\n        MousePos       = 1 << 5,\n        MouseButtonMask= 0b11 << 6,\n\n        Scroll     = 1 << 8,\n        Resize     = 1 << 9,\n        MenuSelect = 1 << 10,\n    };\n    enum NamedKey : Codepoint\n    {\n        // use UTF-16 surrogate pairs range\n        Backspace = 0xD800,\n        Delete,\n        Escape,\n        Return,\n        Up,\n        Down,\n        Left,\n        Right,\n        PageUp,\n        PageDown,\n        Home,\n        End,\n        Insert,\n        Tab,\n        Space,\n        F1,\n        F2,\n        F3,\n        F4,\n        F5,\n        F6,\n        F7,\n        F8,\n        F9,\n        F10,\n        F11,\n        F12,\n        FocusIn,\n        FocusOut,\n        Invalid,\n    };\n\n    Modifiers modifiers = {};\n    Codepoint key = {};\n\n    constexpr Key(Modifiers modifiers, Codepoint key)\n        : modifiers(modifiers), key(key) {}\n\n    constexpr Key(Codepoint key)\n        : modifiers(Modifiers::None), key(key) {}\n\n    constexpr Key() = default;\n\n    constexpr uint64_t val() const { return (uint64_t)modifiers << 32 | key; }\n\n    constexpr bool operator==(Key other) const { return val() == other.val(); }\n    constexpr auto operator<=>(Key other) const { return val() <=> other.val(); }\n\n    constexpr DisplayCoord coord() const { return {(int)((int32_t) (key & 0xFFFF0000) >> 16), (int)(key & 0x0000FFFF)}; }\n    constexpr MouseButton mouse_button() { return MouseButton{((int)modifiers & (int)Modifiers::MouseButtonMask) >> 6}; }\n    constexpr int scroll_amount() { return (int32_t)modifiers >> 16; }\n    static Modifiers to_modifier(MouseButton button) { return Key::Modifiers{((int)button << 6) & (int)Modifiers::MouseButtonMask}; }\n\n    Optional<Codepoint> codepoint() const;\n};\n\nconstexpr bool with_bit_ops(Meta::Type<Key::Modifiers>) { return true; }\n\nusing KeyList = Vector<Key, MemoryDomain::Mapping>;\n\nclass String;\nclass StringView;\n\nKeyList parse_keys(StringView str);\nString  to_string(Key key);\nStringView to_string(Key::MouseButton button);\nKey::MouseButton str_to_button(StringView str);\n\nconstexpr Key shift(Key key)\n{\n    return { key.modifiers | Key::Modifiers::Shift, key.key };\n}\nconstexpr Key alt(Key key)\n{\n    return { key.modifiers | Key::Modifiers::Alt, key.key };\n}\nconstexpr Key ctrl(Key key)\n{\n    return { key.modifiers | Key::Modifiers::Control, key.key };\n}\n\nconstexpr Codepoint encode_coord(DisplayCoord coord) { return (Codepoint)(((int)coord.line << 16) | ((int)coord.column & 0x0000FFFF)); }\n\nconstexpr Key resize(DisplayCoord dim) { return { Key::Modifiers::Resize, encode_coord(dim) }; }\n\nconstexpr size_t hash_value(const Key& key) { return hash_values(key.modifiers, key.key); }\n\n}\n\n#endif // keys_hh_INCLUDED\n"
  },
  {
    "path": "src/line_modification.cc",
    "content": "#include \"line_modification.hh\"\n\n#include \"buffer.hh\"\n#include \"unit_tests.hh\"\n#include \"ranges.hh\"\n\nnamespace Kakoune\n{\n\nstatic LineModification make_line_modif(const Buffer::Change& change)\n{\n    LineCount num_added = 0, num_removed = 0;\n    if (change.type == Buffer::Change::Insert)\n        num_added = change.end.line - change.begin.line;\n    else\n        num_removed = change.end.line - change.begin.line;\n    // modified a line\n    if ((change.begin.column != 0 or change.end.column != 0))\n    {\n        ++num_removed;\n        ++num_added;\n    }\n    return { change.begin.line, change.begin.line, num_removed, num_added };\n}\n\nVector<LineModification> compute_line_modifications(const Buffer& buffer, size_t timestamp)\n{\n    Vector<LineModification> res;\n    for (auto& buf_change : buffer.changes_since(timestamp))\n    {\n        auto change = make_line_modif(buf_change);\n\n        auto pos = std::upper_bound(res.begin(), res.end(), change.new_line,\n                                    [](const LineCount& l, const LineModification& c)\n                                    { return l < c.new_line; });\n\n        if (pos != res.begin())\n        {\n            auto& prev = *(pos-1);\n            if (change.new_line <= prev.new_line + prev.num_added)\n            {\n                --pos;\n                const LineCount removed_from_previously_added_by_pos =\n                    clamp(pos->new_line + pos->num_added - change.new_line,\n                          0_line, std::min(pos->num_added, change.num_removed));\n\n                pos->num_removed += change.num_removed - removed_from_previously_added_by_pos;\n                pos->num_added += change.num_added - removed_from_previously_added_by_pos;\n            }\n            else\n            {\n                change.old_line -= prev.diff();\n                pos = res.insert(pos, change);\n            }\n        }\n        else\n            pos = res.insert(pos, change);\n\n        auto next = pos + 1;\n        auto diff = buf_change.end.line - buf_change.begin.line;\n        if (buf_change.type == Buffer::Change::Erase)\n        {\n            auto delend = std::upper_bound(next, res.end(), change.new_line + change.num_removed,\n                                           [](const LineCount& l, const LineModification& c)\n                                           { return l < c.new_line; });\n\n            for (auto it = next; it != delend; ++it)\n            {\n                const LineCount removed_from_previously_added_by_it =\n                    std::min(it->num_added, change.new_line + change.num_removed - it->new_line);\n\n                pos->num_removed += it->num_removed - removed_from_previously_added_by_it;\n                pos->num_added += it->num_added - removed_from_previously_added_by_it;\n            }\n            next = res.erase(next, delend);\n\n            if (diff != 0)\n            {\n                for (auto it = next; it != res.end(); ++it)\n                    it->new_line -= diff;\n            }\n        }\n        else if (diff != 0)\n        {\n            for (auto it = next; it != res.end(); ++it)\n                it->new_line += diff;\n        }\n    }\n    return res;\n}\n\nvoid LineRangeSet::update(ConstArrayView<LineModification> modifs)\n{\n    if (modifs.empty())\n        return;\n\n    for (auto it = begin(); it != end(); ++it)\n    {\n        auto modif_beg = std::lower_bound(modifs.begin(), modifs.end(), it->begin,\n                                          [](const LineModification& c, const LineCount& l)\n                                          { return c.old_line + c.num_removed < l; });\n        auto modif_end = std::upper_bound(modifs.begin(), modifs.end(), it->end,\n                                          [](const LineCount& l, const LineModification& c)\n                                          { return l < c.old_line; });\n\n        if (modif_beg == modifs.end())\n        {\n            const auto diff = (modif_beg-1)->diff();\n            it->begin += diff;\n            it->end += diff;\n            continue;\n        }\n\n        const auto diff = modif_beg->new_line - modif_beg->old_line;\n        it->begin += diff;\n        it->end += diff;\n\n        while (modif_beg != modif_end)\n        {\n            auto& m = *modif_beg++;\n            if (m.num_removed > 0)\n            {\n                if (m.new_line < it->begin)\n                    it->begin = std::max(m.new_line, it->begin - m.num_removed);\n                it->end = std::max(m.new_line, std::max(it->begin, it->end - m.num_removed));\n            }\n            if (m.num_added > 0)\n            {\n                if (it->begin >= m.new_line)\n                    it->begin += m.num_added;\n                else\n                {\n                    it = insert(it, {it->begin, m.new_line}) + 1;\n                    it->begin = m.new_line + m.num_added;\n                }\n                it->end += m.num_added;\n            }\n        }\n    };\n    erase(remove_if(*this, [](auto& r) { return r.begin >= r.end; }), end());\n}\n\nvoid LineRangeSet::add_range(LineRange range, FunctionRef<void (LineRange)> on_new_range)\n{\n    auto insert_at = std::lower_bound(begin(), end(), range.begin,\n                                      [](LineRange range, LineCount line) { return range.end < line; });\n    if (insert_at == end() or insert_at->begin > range.end)\n        on_new_range(range);\n    else\n    {\n        auto pos = range.begin;\n        auto it = insert_at;\n        for (; it != end() and it->begin <= range.end; ++it)\n        {\n            if (pos < it->begin)\n                on_new_range({pos, it->begin});\n\n            range = LineRange{std::min(range.begin, it->begin), std::max(range.end, it->end)};\n            pos = it->end;\n        }\n        insert_at = erase(insert_at, it);\n        if (pos < range.end)\n            on_new_range({pos, range.end});\n    }\n    insert(insert_at, range);\n}\n\nvoid LineRangeSet::remove_range(LineRange range)\n{\n    auto inside = [](LineCount line, LineRange range) {\n        return range.begin <= line and line < range.end;\n    };\n\n    auto it = std::lower_bound(begin(), end(), range.begin,\n                               [](LineRange range, LineCount line) { return range.end < line; });\n    if (it == end() or it->begin > range.end)\n        return;\n    else while (it != end() and it->begin <= range.end)\n    {\n        if (it->begin < range.begin and range.end <= it->end)\n        {\n            it = insert(it, {it->begin, range.begin}) + 1;\n            it->begin = range.end;\n        }\n        if (inside(it->begin, range))\n            it->begin = range.end;\n        if (inside(it->end, range))\n            it->end = range.begin;\n\n        if (it->end <= it->begin)\n            it = erase(it);\n        else\n            ++it;\n    }\n}\n\n\nUnitTest test_line_modifications{[]()\n{\n    auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create(lines)...}; };\n\n    {\n        Buffer buffer(\"test\", Buffer::Flags::None, make_lines(\"line 1\\n\", \"line 2\\n\"));\n        auto ts = buffer.timestamp();\n        buffer.erase({1, 0}, {2, 0});\n\n        auto modifs = compute_line_modifications(buffer, ts);\n        kak_assert(modifs.size() == 1 and modifs[0] == LineModification{ 1, 1, 1, 0 });\n    }\n\n    {\n        Buffer buffer(\"test\", Buffer::Flags::None, make_lines(\"line 1\\n\", \"line 2\\n\"));\n        auto ts = buffer.timestamp();\n        buffer.insert({2, 0}, \"line 3\");\n\n        auto modifs = compute_line_modifications(buffer, ts);\n        kak_assert(modifs.size() == 1 and modifs[0] == LineModification{ 2, 2, 0, 1 });\n    }\n\n    {\n        Buffer buffer(\"test\", Buffer::Flags::None, make_lines(\"line 1\\n\", \"line 2\\n\", \"line 3\\n\"));\n\n        auto ts = buffer.timestamp();\n        buffer.insert({1, 4}, \"hoho\\nhehe\");\n        buffer.erase({0, 0}, {1, 0});\n\n        auto modifs = compute_line_modifications(buffer, ts);\n        kak_assert(modifs.size() == 1 and modifs[0] == LineModification{ 0, 0, 2, 2 });\n    }\n\n    {\n        Buffer buffer(\"test\", Buffer::Flags::None, make_lines(\"line 1\\n\", \"line 2\\n\", \"line 3\\n\", \"line 4\\n\"));\n\n        auto ts = buffer.timestamp();\n        buffer.erase({0,0}, {3,0});\n        buffer.insert({1,0}, \"newline 1\\nnewline 2\\nnewline 3\\n\");\n        buffer.erase({0,0}, {1,0});\n        {\n            auto modifs = compute_line_modifications(buffer, ts);\n            kak_assert(modifs.size() == 1 and modifs[0] == LineModification{ 0, 0, 4, 3 });\n        }\n        buffer.insert({3,0}, \"newline 4\\n\");\n\n        {\n            auto modifs = compute_line_modifications(buffer, ts);\n            kak_assert(modifs.size() == 1 and modifs[0] == LineModification{ 0, 0, 4, 4 });\n        }\n    }\n\n    {\n        Buffer buffer(\"test\", Buffer::Flags::None, make_lines(\"line 1\\n\"));\n        auto ts = buffer.timestamp();\n        buffer.insert({0,0}, \"n\");\n        buffer.insert({0,1}, \"e\");\n        buffer.insert({0,2}, \"w\");\n        auto modifs = compute_line_modifications(buffer, ts);\n        kak_assert(modifs.size() == 1 and modifs[0] == LineModification{ 0, 0, 1, 1 });\n    }\n}};\n\nUnitTest test_line_range_set{[]{\n    auto expect = [](ConstArrayView<LineRange> ranges) {\n        return [it = ranges.begin(), end = ranges.end()](LineRange r) mutable {\n            kak_assert(it != end);\n            kak_assert(r == *it++);\n        };\n    };\n\n    {\n        LineRangeSet ranges;\n        ranges.add_range({0, 5}, expect({{0, 5}}));\n        ranges.add_range({10, 15}, expect({{10, 15}}));\n        ranges.add_range({5, 10}, expect({{5, 10}}));\n        kak_assert((ranges.view() == ConstArrayView<LineRange>{{0, 15}}));\n        ranges.add_range({5, 10}, expect({}));\n        ranges.remove_range({3, 8});\n        kak_assert((ranges.view() == ConstArrayView<LineRange>{{0, 3}, {8, 15}}));\n    }\n    {\n        LineRangeSet ranges;\n        ranges.add_range({0, 7}, expect({{0, 7}}));\n        ranges.add_range({9, 15}, expect({{9, 15}}));\n        ranges.add_range({5, 10}, expect({{7, 9}}));\n        kak_assert((ranges.view() == ConstArrayView<LineRange>{{0, 15}}));\n    }\n    {\n        LineRangeSet ranges;\n        ranges.add_range({0, 7}, expect({{0, 7}}));\n        ranges.add_range({11, 15}, expect({{11, 15}}));\n        ranges.add_range({5, 10}, expect({{7, 10}}));\n        kak_assert((ranges.view() == ConstArrayView<LineRange>{{0, 10}, {11, 15}}));\n        ranges.remove_range({8, 13});\n        kak_assert((ranges.view() == ConstArrayView<LineRange>{{0, 8}, {13, 15}}));\n    }\n    {\n        LineRangeSet ranges;\n        ranges.add_range({0, 5}, expect({{0, 5}}));\n        ranges.add_range({10, 15}, expect({{10, 15}}));\n        ranges.update(ConstArrayView<LineModification>{{3, 3, 3, 1}, {11, 9, 2, 4}});\n        kak_assert((ranges.view() == ConstArrayView<LineRange>{{0, 3}, {8, 9}, {13, 15}}));\n    }\n    {\n        LineRangeSet ranges;\n        ranges.add_range({0, 5}, expect({{0, 5}}));\n        ranges.update(ConstArrayView<LineModification>{{2, 2, 2, 0}});\n        kak_assert((ranges.view() == ConstArrayView<LineRange>{{0, 3}}));\n    }\n    {\n        LineRangeSet ranges;\n        ranges.add_range({0, 5}, expect({{0, 5}}));\n        ranges.update(ConstArrayView<LineModification>{{2, 2, 0, 2}});\n        kak_assert((ranges.view() == ConstArrayView<LineRange>{{0, 2}, {4, 7}}));\n    }\n    {\n        LineRangeSet ranges;\n        ranges.add_range({0, 1}, expect({{0, 1}}));\n        ranges.add_range({5, 10}, expect({{5, 10}}));\n        ranges.add_range({15, 20}, expect({{15, 20}}));\n        ranges.add_range({25, 30}, expect({{25, 30}}));\n        ranges.update(ConstArrayView<LineModification>{{2, 2, 3, 0}});\n        kak_assert((ranges.view() == ConstArrayView<LineRange>{{0, 1}, {2, 7}, {12, 17}, {22, 27}}));\n    }\n}};\n\n}\n"
  },
  {
    "path": "src/line_modification.hh",
    "content": "#ifndef line_change_watcher_hh_INCLUDED\n#define line_change_watcher_hh_INCLUDED\n\n#include \"array_view.hh\"\n#include \"units.hh\"\n#include \"utils.hh\"\n#include \"range.hh\"\n#include \"vector.hh\"\n\nnamespace Kakoune\n{\n\nclass Buffer;\n\nstruct LineModification\n{\n    LineCount old_line; // line position in the old buffer\n    LineCount new_line; // new line position\n    LineCount num_removed; // number of lines removed (including this one)\n    LineCount num_added; // number of lines added (including this one)\n\n    LineCount diff() const { return new_line - old_line + num_added - num_removed; }\n\n    friend bool operator==(const LineModification& lhs, const LineModification& rhs) = default;\n};\n\nVector<LineModification> compute_line_modifications(const Buffer& buffer, size_t timestamp);\n\nusing LineRange = Range<LineCount>;\n\nstruct LineRangeSet : private Vector<LineRange, MemoryDomain::Highlight>\n{\n    using Base = Vector<LineRange, MemoryDomain::Highlight>;\n    using Base::operator[];\n    using Base::begin;\n    using Base::end;\n\n    ConstArrayView<LineRange> view() const { return {data(), data() + size()}; }\n\n    void reset(LineRange range) { Base::operator=({range}); }\n\n    void update(ConstArrayView<LineModification> modifs);\n    void add_range(LineRange range, FunctionRef<void (LineRange)> on_new_range);\n    void remove_range(LineRange range);\n};\n\n\n}\n\n#endif // line_change_watcher_hh_INCLUDED\n"
  },
  {
    "path": "src/main.cc",
    "content": "#include \"assert.hh\"\n#include \"backtrace.hh\"\n#include \"buffer.hh\"\n#include \"buffer_utils.hh\"\n#include \"buffer_manager.hh\"\n#include \"client_manager.hh\"\n#include \"command_manager.hh\"\n#include \"commands.hh\"\n#include \"context.hh\"\n#include \"debug.hh\"\n#include \"event_manager.hh\"\n#include \"face_registry.hh\"\n#include \"file.hh\"\n#include \"hook_manager.hh\"\n#include \"highlighters.hh\"\n#include \"insert_completer.hh\"\n#include \"json_ui.hh\"\n#include \"keymap_manager.hh\"\n#include \"terminal_ui.hh\"\n#include \"option_manager.hh\"\n#include \"option_types.hh\"\n#include \"parameters_parser.hh\"\n#include \"profile.hh\"\n#include \"ranges.hh\"\n#include \"regex.hh\"\n#include \"register_manager.hh\"\n#include \"remote.hh\"\n#include \"scope.hh\"\n#include \"shared_string.hh\"\n#include \"shell_manager.hh\"\n#include \"string.hh\"\n#include \"unit_tests.hh\"\n#include \"window.hh\"\n\n#include <fcntl.h>\n#include <locale.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <pwd.h>\n\nnamespace Kakoune\n{\n\nextern const char* version;\n\nnamespace\n{\n\nstruct {\n    unsigned int version;\n    StringView notes;\n} constexpr version_notes[] = { {\n        0,\n        \"» {+u}finaleol{} option to preserve files with no final end-of-line\\n\"\n        \"» {+b}%val\\\\{buffile}{} is now empty for scratch buffers\\n\"\n        \"» {+b}FocusIn{}/{+b}FocusOut{} events on suspend\\n\"\n        \"» {+u}number-lines -full-relative{} switch to keep a smaller line number gutter\\n\"\n        \"» {+b}<a-I>{} and {+b}<a-A>{} to select nested text objects\\n\"\n        \"» {+b}kak -C <session>{} to connect-or-create a session\\n\"\n    }, {\n        20250603,\n        \"» kak_* appearing in shell arguments will be added to the environment\\n\"\n        \"» {+U}double underline{} support\\n\"\n        \"» {+u}git apply{} can stage/revert selected changes to current buffer\\n\"\n        \"» {+u}exec/eval -client{} accepts '*' and comma separated list\\n\"\n    }, {\n        20240518,\n        \"» Fix tests failing on some platforms\\n\"\n    }, {\n        20240509,\n        \"» {+u}flag-lines -after{} highlighter\\n\"\n        \"» asynchronous {+u}shell-script-candidates{} completion\\n\"\n        \"» {+b}%val\\\\{window_range}{} is now emitted as separate strings\\n\"\n        \"» {+b}+{} only duplicates identical selections a single time\\n\"\n        \"» {+u}daemonize-session{} command\\n\"\n        \"» view mode and mouse scrolling no longer change selections\\n\"\n        \"» {+u}git apply/blame-jump/edit/grep{} commands\\n\"\n        \"» {+u}git blame{} works in {+u}git-diff{} and {+u}git-log{} buffers\\n\"\n        \"» custom completions are no longer sorted if the typed text is empty\\n\"\n        \"» {+u}terminal{} now selects implementation based on windowing options\\n\"\n        \"» {+u}local{} scopes\\n\"\n    }\n};\n\nstatic_assert(sizeof(version_notes) / sizeof(version_notes[0]) <= 4 - (version_notes[0].version != 0 ? 1 : 0),\n              \"Maximum 3 versions should be displayed in version notes, not including the development version\");\n\nvoid show_startup_info(Client* local_client, int last_version)\n{\n    const Face version_face{.attributes=Attribute::Bold};\n    DisplayLineList info;\n    for (auto [version, notes] : version_notes)\n    {\n        if (version and version <= last_version)\n            continue;\n\n        if (not version)\n            info.push_back({\"• Development version\", version_face});\n        else\n        {\n            const auto year = version / 10000;\n            const auto month = (version / 100) % 100;\n            const auto day = version % 100;\n            info.push_back({format(\"• Kakoune v{}.{:02}.{:02}\", year, month, day), version_face});\n        }\n\n        for (auto&& line : notes | split<StringView>('\\n'))\n            info.push_back(parse_display_line(line, GlobalScope::instance().faces()));\n    }\n    if (not info.empty())\n        local_client->info_show({{{format(\"Kakoune {}\", version), version_face},\n                                  {\", more info at \", {}},\n                                  {\":doc changelog\", {.attributes=Attribute::Underline}}}},\n                                 std::move(info), {}, InfoStyle::Prompt);\n}\n\ninline void write_stdout(StringView str) { try { write(STDOUT_FILENO, str); } catch (runtime_error&) {} }\ninline void write_stderr(StringView str) { try { write(STDERR_FILENO, str); } catch (runtime_error&) {} }\n\nString runtime_directory()\n{\n    if (const char* runtime_directory = getenv(\"KAKOUNE_RUNTIME\"))\n        return runtime_directory;\n\n    char relpath[PATH_MAX+1];\n    format_to(relpath, \"{}../share/kak\", split_path(get_kak_binary_path()).first);\n    struct stat st;\n    if (stat(relpath, &st) == 0 and S_ISDIR(st.st_mode))\n        return real_path(relpath);\n\n    return \"/usr/share/kak\";\n}\n\nString config_directory()\n{\n    if (StringView kak_cfg_dir = getenv(\"KAKOUNE_CONFIG_DIR\"); not kak_cfg_dir.empty())\n        return kak_cfg_dir.str();\n    if (StringView xdg_cfg_home = getenv(\"XDG_CONFIG_HOME\"); not xdg_cfg_home.empty())\n        return format(\"{}/kak\", xdg_cfg_home);\n    return format(\"{}/.config/kak\", homedir());\n}\n\nauto main_sel_first(const SelectionList& selections)\n{\n    auto beg = &*selections.begin(), end = &*selections.end();\n    auto main = beg + selections.main_index();\n    using View = ConstArrayView<Selection>;\n    return concatenated(View{main, end}, View{beg, main});\n}\n\nconst EnvVarDesc builtin_env_vars[] = { {\n        \"bufname\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {context.buffer().display_name()}; }\n    }, {\n        \"buffile\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {context.buffer().filename()}; }\n    }, {\n        \"buflist\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return BufferManager::instance() | transform(&Buffer::display_name) | gather<Vector<String>>(); }\n    }, {\n        \"buf_line_count\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string(context.buffer().line_count())}; }\n    }, {\n        \"timestamp\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string(context.buffer().timestamp())}; }\n    }, {\n        \"history_id\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string((size_t)context.buffer().current_history_id())}; }\n    }, {\n        \"selection\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { const Selection& sel = context.selections().main();\n          return {content(context.buffer(), sel)}; }\n    }, {\n        \"selections\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return context.selections_content(); }\n    }, {\n        \"runtime\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {runtime_directory()}; }\n    }, {\n        \"config\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {config_directory()}; }\n    }, {\n        \"version\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {version}; }\n    }, {\n        \"opt_\", true,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return context.options()[name.substr(4_byte)].get_as_strings(); }\n    }, {\n        \"main_reg_\", true,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {context.main_sel_register_value(name.substr(9_byte)).str()}; }\n    }, {\n        \"reg_\", true,\n        [](StringView name, const Context& context)\n        { return RegisterManager::instance()[name.substr(4_byte)].get(context) |\n                     gather<Vector<String>>(); }\n    }, {\n        \"client_env_\", true,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {context.client().get_env_var(name.substr(11_byte)).str()}; }\n    }, {\n        \"session\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {Server::instance().session()}; }\n    }, {\n        \"client\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {context.name()}; }\n    }, {\n        \"client_pid\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string(context.client().pid())}; }\n    }, {\n        \"client_list\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return ClientManager::instance() |\n                      transform([](const UniquePtr<Client>& c) -> const String&\n                                { return c->context().name(); }) | gather<Vector<String>>(); }\n    }, {\n        \"modified\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {context.buffer().is_modified() ? \"true\" : \"false\"}; }\n    }, {\n        \"cursor_line\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string(context.selections().main().cursor().line + 1)}; }\n    }, {\n        \"cursor_column\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string(context.selections().main().cursor().column + 1)}; }\n    }, {\n        \"cursor_char_value\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { auto coord = context.selections().main().cursor();\n          auto& buffer = context.buffer();\n          return {to_string((size_t)utf8::codepoint(buffer.iterator_at(coord), buffer.end()))}; }\n    }, {\n        \"cursor_char_column\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { auto coord = context.selections().main().cursor();\n          return {to_string(context.buffer()[coord.line].char_count_to(coord.column) + 1)}; }\n    }, {\n        \"cursor_display_column\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { auto coord = context.selections().main().cursor();\n          return {to_string(get_column(context.buffer(),\n                                       context.options()[\"tabstop\"].get<int>(),\n                                       coord) + 1)}; }\n    }, {\n        \"cursor_byte_offset\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { auto cursor = context.selections().main().cursor();\n          return {to_string(context.buffer().distance({0,0}, cursor))}; }\n    }, {\n        \"recording_register\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { const char reg = context.client().input_handler().recording_reg();\n          return {reg ? String{static_cast<Codepoint>(reg)} : \"\"}; }\n    }, {\n        \"selection_desc\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {selection_to_string(ColumnType::Byte, context.buffer(), context.selections().main())}; }\n    }, {\n        \"selections_desc\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return main_sel_first(context.selections()) |\n                     transform([&buffer=context.buffer()](const Selection& sel) {\n                         return selection_to_string(ColumnType::Byte, buffer, sel);\n                     }) | gather<Vector<String>>(); }\n    }, {\n        \"selections_char_desc\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return main_sel_first(context.selections()) |\n                     transform([&buffer=context.buffer()](const Selection& sel) {\n                         return selection_to_string(ColumnType::Codepoint, buffer, sel);\n                     }) | gather<Vector<String>>(); }\n    }, {\n        \"selections_display_column_desc\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return main_sel_first(context.selections()) |\n                     transform([&buffer=context.buffer(), tabstop=context.options()[\"tabstop\"].get<int>()](const Selection& sel) {\n                         return selection_to_string(ColumnType::DisplayColumn, buffer, sel, tabstop);\n                     }) | gather<Vector<String>>(); }\n    }, {\n        \"selection_length\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string(char_length(context.buffer(), context.selections().main()))}; }\n    }, {\n        \"selections_length\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return context.selections() |\n                     transform([&](const Selection& s) -> String {\n                         return to_string(char_length(context.buffer(), s));\n                     }) | gather<Vector<String>>(); }\n    }, {\n        \"selection_count\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string(context.selections().size())}; }\n    }, {\n        \"window_width\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string(context.window().dimensions().column)}; }\n    }, {\n        \"window_height\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return {to_string(context.window().dimensions().line)}; }\n    }, {\n        \"user_modes\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return context.keymaps().user_modes(); }\n    }, {\n        \"window_range\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        {\n            const auto& setup = context.window().last_display_setup();\n            const auto width = context.window().dimensions().column - setup.widget_columns;\n            return {to_string(setup.first_line), to_string(setup.first_column),\n                    to_string(setup.line_count), to_string(width)};\n        }\n    }, {\n        \"history\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return history_as_strings(context.buffer().history()); }\n    }, {\n        \"history_since_\", true,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return history_as_strings(\n            ArrayView(context.buffer().history())\n                .subrange(str_to_int(name.substr(14_byte)) + 1)\n        ); }\n    }, {\n        \"uncommitted_modifications\", false,\n        [](StringView name, const Context& context) -> Vector<String>\n        { return undo_group_as_strings(context.buffer().current_undo_group()); }\n    }\n};\n\nvoid register_registers()\n{\n    RegisterManager& register_manager = RegisterManager::instance();\n\n    for (Codepoint c : StringView{\"abcdefghijklmnopqrstuvwxyz\\\"^@\"})\n        register_manager.add_register(c, make_unique_ptr<StaticRegister>(String{c}));\n\n    for (Codepoint c : StringView{\"/|:\\\\\"})\n        register_manager.add_register(c, make_unique_ptr<HistoryRegister>(String{c}));\n\n    using StringList = Vector<String, MemoryDomain::Registers>;\n\n    register_manager.add_register('%', make_dyn_reg(\n        \"%\",\n        [](const Context& context)\n        { return StringList{{context.buffer().display_name()}}; }));\n\n    register_manager.add_register('.', make_dyn_reg(\n        \".\",\n        [](const Context& context) {\n            auto content = context.selections_content();\n            return StringList{content.begin(), content.end()};\n         }));\n\n    register_manager.add_register('#', make_dyn_reg(\n        \"#\",\n        [](const Context& context) {\n            const size_t count = context.selections().size();\n            StringList res;\n            res.reserve(count);\n            for (size_t i = 1; i < count+1; ++i)\n                res.push_back(to_string((int)i));\n            return res;\n        }));\n\n    for (size_t i = 0; i < 10; ++i)\n    {\n        register_manager.add_register('0'+i, make_dyn_reg(\n            String{Codepoint('0'+i)},\n            [i](const Context& context) {\n                StringList result;\n                for (auto& sel : context.selections())\n                    result.emplace_back(i < sel.captures().size() ? sel.captures()[i] : \"\");\n                return result;\n            },\n            [i](Context& context, ConstArrayView<String> values) {\n                if (values.empty())\n                    return;\n\n                auto& sels = context.selections();\n                for (size_t sel_index = 0; sel_index < sels.size(); ++sel_index)\n                {\n                    auto& sel = sels[sel_index];\n                    if (sel.captures().size() < i+1)\n                        sel.captures().resize(i+1);\n                    sel.captures()[i] = values[std::min(sel_index, values.size()-1)];\n                }\n            }));\n    }\n\n    register_manager.add_register('_', make_unique_ptr<NullRegister>());\n}\n\nvoid register_keymaps()\n{\n    auto& keymaps = GlobalScope::instance().keymaps();\n    keymaps.map_key(Key::Left, KeymapMode::Normal, {'h'}, \"\");\n    keymaps.map_key(Key::Right, KeymapMode::Normal, {'l'}, \"\");\n    keymaps.map_key(Key::Down, KeymapMode::Normal, {'j'}, \"\");\n    keymaps.map_key(Key::Up, KeymapMode::Normal, {'k'}, \"\");\n\n    keymaps.map_key(shift(Key::Left), KeymapMode::Normal, {'H'}, \"\");\n    keymaps.map_key(shift(Key::Right), KeymapMode::Normal, {'L'}, \"\");\n    keymaps.map_key(shift(Key::Down), KeymapMode::Normal, {'J'}, \"\");\n    keymaps.map_key(shift(Key::Up), KeymapMode::Normal, {'K'}, \"\");\n\n    keymaps.map_key(Key::End, KeymapMode::Normal, {alt('l')}, \"\");\n    keymaps.map_key(Key::Home, KeymapMode::Normal, {alt('h')}, \"\");\n    keymaps.map_key(shift(Key::End), KeymapMode::Normal, {alt('L')}, \"\");\n    keymaps.map_key(shift(Key::Home), KeymapMode::Normal, {alt('H')}, \"\");\n}\n\nvoid check_tabstop(const int& val)\n{\n    if (val < 1) throw runtime_error{\"tabstop should be strictly positive\"};\n}\n\nvoid check_indentwidth(const int& val)\n{\n    if (val < 0) throw runtime_error{\"indentwidth should be positive or zero\"};\n}\n\nvoid check_scrolloff(const DisplayCoord& so)\n{\n    if (so.line < 0 or so.column < 0)\n        throw runtime_error{\"scroll offset must be positive or zero\"};\n}\n\nvoid check_timeout(const int& timeout)\n{\n    if (timeout < 50)\n        throw runtime_error{\"the minimum acceptable timeout is 50 milliseconds\"};\n}\n\nvoid check_extra_word_chars(const Vector<Codepoint, MemoryDomain::Options>& extra_chars)\n{\n    if (any_of(extra_chars, is_blank))\n        throw runtime_error{\"blanks are not accepted for extra completion characters\"};\n}\n\nvoid check_matching_pairs(const Vector<Codepoint, MemoryDomain::Options>& pairs)\n{\n    if ((pairs.size() % 2) != 0)\n        throw runtime_error{\"matching pairs should have a pair number of element\"};\n    if (not all_of(pairs, [](Codepoint cp) { return is_punctuation(cp); }))\n        throw runtime_error{\"matching pairs can only be punctuation\"};\n}\n\nvoid register_options()\n{\n    OptionsRegistry& reg = GlobalScope::instance().option_registry();\n\n    reg.declare_option<int, check_tabstop>(\"tabstop\", \"size of a tab character\", 8);\n    reg.declare_option<int, check_indentwidth>(\"indentwidth\", \"indentation width\", 4);\n    reg.declare_option<DisplayCoord, check_scrolloff>(\n        \"scrolloff\", \"number of lines and columns to keep visible main cursor when scrolling\",\n        {0,0});\n    reg.declare_option(\"eolformat\", \"end of line format\", EolFormat::Lf);\n    reg.declare_option(\"finaleol\", \"write and end of line at end of file\", FinalEol::Present);\n    reg.declare_option(\"BOM\", \"byte order mark to use when writing buffer\",\n                       ByteOrderMark::None);\n    reg.declare_option(\"incsearch\",\n                       \"incrementally apply search/select/split regex\",\n                       true);\n    reg.declare_option(\"autoinfo\",\n                       \"automatically display contextual help\",\n                       AutoInfo::Command | AutoInfo::OnKey);\n    reg.declare_option(\"autocomplete\",\n                       \"automatically display possible completions\",\n                       AutoComplete::Insert | AutoComplete::Prompt);\n    reg.declare_option(\"aligntab\",\n                       \"use tab characters when possible for alignment\",\n                       false);\n    reg.declare_option(\"ignored_files\",\n                       \"patterns to ignore when completing filenames\",\n                       Regex{R\"(^(\\..*|.*\\.(o|so|a))$)\"});\n    reg.declare_option(\"disabled_hooks\",\n                      \"patterns to disable hooks whose group is matched\",\n                      Regex{});\n    reg.declare_option(\"filetype\", \"buffer filetype\", \"\"_str);\n    reg.declare_option(\"path\", \"path to consider when trying to find a file\",\n                   Vector<String, MemoryDomain::Options>({ \"./\", \"%/\", \"/usr/include\" }));\n    reg.declare_option(\"completers\", \"insert mode completers to execute.\",\n                       InsertCompleterDescList({\n                           InsertCompleterDesc{ InsertCompleterDesc::Filename, {} },\n                           InsertCompleterDesc{ InsertCompleterDesc::Word, \"all\"_str }\n                       }), OptionFlags::None);\n    reg.declare_option(\"static_words\", \"list of words to always consider for insert word completion\",\n                   Vector<String, MemoryDomain::Options>{});\n    reg.declare_option(\"autoreload\",\n                       \"autoreload buffer when a filesystem modification is detected\",\n                       Autoreload::Ask);\n    reg.declare_option(\"writemethod\",\n                       \"how to write buffer to files\",\n                       WriteMethod::Overwrite);\n    reg.declare_option<int, check_timeout>(\n        \"idle_timeout\", \"timeout, in milliseconds, before idle hooks are triggered\", 50);\n    reg.declare_option<int, check_timeout>(\n        \"fs_check_timeout\", \"timeout, in milliseconds, between file system buffer modification checks\",\n        500);\n    reg.declare_option(\"ui_options\",\n                       \"space separated list of <key>=<value> options that are \"\n                       \"passed to and interpreted by the user interface\\n\"\n                       \"\\n\"\n                       \"The terminal ui supports the following options:\\n\"\n                       \"    <key>:                        <value>:\\n\"\n                       \"    terminal_assistant             clippy|cat|dilbert|none|off\\n\"\n                       \"    terminal_status_on_top         bool\\n\"\n                       \"    terminal_set_title             bool\\n\"\n                       \"    terminal_title                 str\\n\"\n                       \"    terminal_enable_mouse          bool\\n\"\n                       \"    terminal_synchronized          bool\\n\"\n                       \"    terminal_wheel_scroll_amount   int\\n\"\n                       \"    terminal_shift_function_key    int\\n\"\n                       \"    terminal_padding_char          codepoint\\n\"\n                       \"    terminal_padding_fill          bool\\n\"\n                       \"    terminal_cursor_native         bool\\n\"\n                       \"    terminal_info_max_width        int\\n\",\n                       UserInterface::Options{});\n    reg.declare_option(\"modelinefmt\", \"format string used to generate the modeline\",\n                       \"%val{bufname} %val{cursor_line}:%val{cursor_char_column} {{context_info}} {{mode_info}} - %val{client}@[%val{session}]\"_str);\n\n    reg.declare_option(\"debug\", \"various debug flags\", DebugFlags::None);\n    reg.declare_option(\"readonly\", \"prevent buffers from being modified\", false);\n    reg.declare_option<Vector<Codepoint, MemoryDomain::Options>, check_extra_word_chars>(\n        \"extra_word_chars\",\n        \"Additional characters to be considered as words for insert completion\",\n        { '_' });\n    reg.declare_option<Vector<Codepoint, MemoryDomain::Options>, check_matching_pairs>(\n        \"matching_pairs\",\n        \"set of pair of characters to be considered as matching pairs\",\n        { '(', ')', '{', '}', '[', ']', '<', '>' });\n    reg.declare_option<int>(\"startup_info_version\", \"version up to which startup info changes should be hidden\", 0);\n}\n\nClient* local_client = nullptr;\nbool convert_to_client_pending = false;\n\nenum class UIType\n{\n    Terminal,\n    Json,\n    Dummy,\n};\n\nUIType parse_ui_type(StringView ui_name)\n{\n    if (ui_name == \"terminal\") return UIType::Terminal;\n    if (ui_name == \"json\") return UIType::Json;\n    if (ui_name == \"dummy\") return UIType::Dummy;\n\n    throw parameter_error(format(\"error: unknown ui type: '{}'\", ui_name));\n}\n\nUniquePtr<UserInterface> make_ui(UIType ui_type)\n{\n    struct DummyUI : UserInterface\n    {\n        DummyUI() { set_signal_handler(SIGINT, SIG_DFL); }\n        bool is_ok() const override { return true; }\n        void menu_show(ConstArrayView<DisplayLine>, DisplayCoord,\n                       Face, Face, MenuStyle) override {}\n        void menu_select(int) override {}\n        void menu_hide() override {}\n\n        void info_show(const DisplayLine&, const DisplayLineList&, DisplayCoord, Face, InfoStyle) override {}\n        void info_hide() override {}\n\n        void draw(const DisplayBuffer&, DisplayCoord, const Face&, const Face&, ColumnCount) override {}\n        void draw_status(const DisplayLine&, const DisplayLine&, const ColumnCount, const DisplayLine&, const Face&) override {}\n        DisplayCoord dimensions() override { return {24,80}; }\n        void refresh(bool) override {}\n        void set_on_key(OnKeyCallback) override {}\n        void set_on_paste(OnPasteCallback) override {}\n        void set_ui_options(const Options&) override {}\n    };\n\n    switch (ui_type)\n    {\n        case UIType::Terminal: return make_unique_ptr<TerminalUI>();\n        case UIType::Json: return make_unique_ptr<JsonUI>();\n        case UIType::Dummy: return make_unique_ptr<DummyUI>();\n    }\n    throw logic_error{};\n}\n\npid_t fork_server_to_background()\n{\n    if (pid_t pid = fork())\n        return pid;\n\n    setsid();\n    if (fork()) // double fork to orphan the server\n        exit(0);\n\n    write_stderr(format(\"Kakoune forked server to background ({}), for session '{}'\\n\",\n                        getpid(), Server::instance().session()));\n    return 0;\n}\n\nUniquePtr<UserInterface> create_local_ui(UIType ui_type)\n{\n    auto ui = make_ui(ui_type);\n\n    static SignalHandler old_handler = set_signal_handler(SIGTSTP, [](int sig) {\n        if (ClientManager::instance().count() == 1 and\n            *ClientManager::instance().begin() == local_client and\n            not Server::instance().is_daemon())\n            old_handler(sig);\n        else\n        {\n            convert_to_client_pending = true;\n            set_signal_handler(SIGTSTP, old_handler);\n        }\n    });\n    return ui;\n}\n\nint run_client(StringView session, StringView name, StringView client_init,\n               Optional<BufferCoord> init_coord, UIType ui_type,\n               bool suspend)\n{\n    try\n    {\n        Optional<int> stdin_fd;\n        // json-ui (or dummy) is not intended to be user interactive.\n        // So only worry about making the tty your stdin if:\n        // (a) ui_type is Terminal, *and*\n        // (b) fd 0 is not interactive.\n        if (ui_type == UIType::Terminal && not isatty(0))\n        {\n            // move stdin to another fd, and restore tty as stdin\n            stdin_fd = dup(0);\n            int tty = open(\"/dev/tty\", O_RDONLY);\n            dup2(tty, 0);\n            close(tty);\n        }\n\n        EventManager event_manager;\n        RemoteClient client{session, name, make_ui(ui_type), getpid(), get_env_vars(),\n                            client_init, std::move(init_coord), stdin_fd};\n        stdin_fd.map(close);\n\n        if (suspend)\n            raise(SIGTSTP);\n        while (not client.exit_status() and client.is_ui_ok())\n            event_manager.handle_next_events(EventMode::Normal);\n        return client.exit_status().value_or(-1);\n    }\n    catch (disconnected& e)\n    {\n        write_stderr(format(\"{}\\ndisconnecting\\n\", e.what()));\n        return -1;\n    }\n}\n\nstruct convert_to_client_mode\n{\n    String session;\n    String client_name;\n    String buffer_name;\n    String selections;\n};\n\nenum class ServerFlags\n{\n    None        = 0,\n    IgnoreKakrc = 1 << 0,\n    Daemon      = 1 << 1,\n    ReadOnly    = 1 << 2,\n    StartupInfo = 1 << 3,\n};\nconstexpr bool with_bit_ops(Meta::Type<ServerFlags>) { return true; }\n\nint run_server(StringView session, StringView server_init,\n               StringView client_init, StringView init_buffer, Optional<BufferCoord> init_coord,\n               ServerFlags flags, UIType ui_type, DebugFlags debug_flags,\n               ConstArrayView<StringView> files)\n{\n    static bool terminate = false;\n    set_signal_handler(SIGTERM, [](int) { terminate = true; });\n    set_signal_handler(SIGINT, [](int) { terminate = true; });\n    if ((flags & ServerFlags::Daemon) and session.empty())\n    {\n        write_stderr(\"-d needs a session name to be specified with -s\\n\");\n        return -1;\n    }\n\n    EventManager        event_manager;\n    Server              server{session.empty() ? to_string(getpid()) : session.str(),\n                               (bool)(flags & ServerFlags::Daemon)};\n\n    StringRegistry      string_registry;\n    GlobalScope         global_scope;\n    ShellManager        shell_manager{builtin_env_vars};\n    CommandManager      command_manager;\n    RegisterManager     register_manager;\n    HighlighterRegistry highlighter_registry;\n    SharedHighlighters  defined_highlighters;\n    ClientManager       client_manager;\n    BufferManager       buffer_manager;\n\n    register_options();\n    register_registers();\n    register_keymaps();\n    register_commands();\n    register_highlighters();\n\n    global_scope.options()[\"debug\"].set(debug_flags);\n\n    write_to_debug_buffer(\"*** This is the debug buffer, where debug info will be written ***\");\n\n#ifdef KAK_DEBUG\n    {\n        ProfileScope profile{debug_flags, [&](std::chrono::microseconds duration) {\n            write_to_debug_buffer(format(\"running the unit tests took {} ms\", duration.count()));\n        }};\n        UnitTest::run_all_tests();\n    }\n#endif\n\n    bool startup_error = false;\n    if (not (flags & ServerFlags::IgnoreKakrc)) try\n    {\n        Context init_context{Context::EmptyContextFlag{}};\n        command_manager.execute(format(\"source {}/kakrc\", runtime_directory()),\n                                init_context);\n    }\n    catch (runtime_error& error)\n    {\n        startup_error = true;\n        write_to_debug_buffer(format(\"error while parsing kakrc:\\n\"\n                                     \"    {}\", error.what()));\n    }\n\n    {\n        Context empty_context{Context::EmptyContextFlag{}};\n        global_scope.hooks().run_hook(Hook::EnterDirectory, real_path(\".\"), empty_context);\n        global_scope.hooks().run_hook(Hook::KakBegin, session, empty_context);\n    }\n\n    if (not server_init.empty()) try\n    {\n        Context init_context{Context::EmptyContextFlag{}};\n        command_manager.execute(server_init, init_context);\n    }\n    catch (const kill_session& kill)\n    {\n        Context empty_context{Context::EmptyContextFlag{}};\n        global_scope.hooks().run_hook(Hook::KakEnd, \"\", empty_context);\n        return kill.exit_status;\n    }\n    catch (runtime_error& error)\n    {\n        startup_error = true;\n        write_to_debug_buffer(format(\"error while running server init commands:\\n\"\n                                     \"    {}\", error.what()));\n    }\n\n    if (not files.empty()) try\n    {\n        for (auto& file : files)\n        {\n            try\n            {\n                Buffer *buffer = open_or_create_file_buffer(file);\n                if (flags & ServerFlags::ReadOnly)\n                {\n                    buffer->flags() |= Buffer::Flags::ReadOnly;\n                    buffer->options().get_local_option(\"readonly\").set(true);\n                }\n            }\n            catch (runtime_error& error)\n            {\n                startup_error = true;\n                write_to_debug_buffer(format(\"error while opening file '{}':\\n\"\n                                             \"    {}\", file, error.what()));\n            }\n        }\n    }\n    catch (runtime_error& error)\n    {\n         write_to_debug_buffer(format(\"error while opening command line files: {}\", error.what()));\n    }\n\n    int exit_status = 0;\n    try\n    {\n        if (ui_type == UIType::Terminal and not isatty(0))\n        {\n            // move stdin to another fd, and restore tty as stdin\n            int fd = dup(0);\n            int tty = open(\"/dev/tty\", O_RDONLY);\n            dup2(tty, 0);\n            close(tty);\n            create_fifo_buffer(\"*stdin*\", fd, Buffer::Flags::None, AutoScroll::NotInitially);\n        }\n\n        if (not server.is_daemon())\n        {\n            local_client = client_manager.create_client(\n                 create_local_ui(ui_type), getpid(), {}, get_env_vars(), client_init, init_buffer, std::move(init_coord),\n                 [&](int status) { exit_status = status; });\n\n            if (startup_error and local_client)\n                local_client->print_status({}, {\n                    \"error during startup, see `:buffer *debug*` for details\",\n                    local_client->context().faces()[\"Error\"]\n                }, -1);\n\n            if (flags & ServerFlags::StartupInfo and local_client)\n                show_startup_info(local_client, global_scope.options()[\"startup_info_version\"].get<int>());\n        }\n\n        while (not terminate and\n               (not client_manager.empty() or server.negotiating() or server.is_daemon()))\n        {\n            client_manager.redraw_clients();\n\n            // Loop so that eventual inputs happening during the processing are handled as\n            // well, avoiding unneeded redraws.\n            Optional<std::chrono::nanoseconds> timeout;\n            if (client_manager.has_pending_inputs())\n                timeout = std::chrono::nanoseconds{};\n            try\n            {\n                while (event_manager.handle_next_events(EventMode::Normal, nullptr, timeout))\n                {\n                    if (client_manager.process_pending_inputs())\n                        break;\n                    timeout = std::chrono::nanoseconds{};\n                }\n            }\n            catch (const cancel&) {}\n\n            client_manager.process_pending_inputs();\n\n            client_manager.clear_client_trash();\n            client_manager.clear_window_trash();\n            buffer_manager.clear_buffer_trash();\n            global_scope.option_registry().clear_option_trash();\n\n            if (local_client and not contains(client_manager, local_client))\n            {\n                local_client = nullptr;\n                if ((not client_manager.empty() or server.is_daemon()) and fork_server_to_background())\n                    exit(exit_status); // We do not want to run destructors and hooks here\n            }\n            else if (convert_to_client_pending)\n            {\n                kak_assert(local_client);\n                auto& local_context = local_client->context();\n                String client_name = local_context.name();\n                String buffer_name = local_context.buffer().name();\n                String selections = selection_list_to_string(ColumnType::Byte, local_context.selections());\n\n                ClientManager::instance().remove_client(*local_client, true, 0);\n                client_manager.clear_client_trash();\n                local_client = nullptr;\n                convert_to_client_pending = false;\n\n                if (fork_server_to_background())\n                {\n                    ClientManager::instance().clear(false);\n                    String session = server.session();\n                    server.close_session(false);\n                    throw convert_to_client_mode{ std::move(session), std::move(client_name), std::move(buffer_name), std::move(selections) };\n                }\n            }\n        }\n    }\n    catch (const kill_session& kill)\n    {\n        exit_status = kill.exit_status;\n    }\n\n    {\n        Context empty_context{Context::EmptyContextFlag{}};\n        global_scope.hooks().run_hook(Hook::KakEnd, \"\", empty_context);\n    }\n\n    return exit_status;\n}\n\nint run_filter(StringView keystr, ConstArrayView<StringView> files, bool quiet, StringView suffix_backup)\n{\n    StringRegistry  string_registry;\n    GlobalScope     global_scope;\n    EventManager    event_manager;\n    ShellManager    shell_manager{builtin_env_vars};\n    RegisterManager register_manager;\n    BufferManager   buffer_manager;\n\n    register_options();\n    register_registers();\n\n    try\n    {\n        auto keys = parse_keys(keystr);\n\n        auto apply_to_buffer = [&](Buffer& buffer)\n        {\n            try\n            {\n                InputHandler input_handler{\n                    { buffer, Selection{{0,0}, buffer.back_coord()} },\n                    Context::Flags::Draft\n                };\n\n                for (auto& key : keys)\n                    input_handler.handle_key(key);\n            }\n            catch (runtime_error& err)\n            {\n                if (not quiet)\n                    write_stderr(format(\"error while applying keys to buffer '{}': {}\\n\",\n                                        buffer.display_name(), err.what()));\n            }\n        };\n\n        for (auto& file : files)\n        {\n            Buffer* buffer = open_file_buffer(file, Buffer::Flags::NoHooks);\n            if (not suffix_backup.empty())\n                write_buffer_to_file(*buffer, buffer->filename() + suffix_backup,\n                                     WriteMethod::Overwrite, WriteFlags::None);\n            apply_to_buffer(*buffer);\n            write_buffer_to_file(*buffer, buffer->filename(),\n                                 WriteMethod::Overwrite, WriteFlags::None);\n            buffer_manager.delete_buffer(*buffer);\n        }\n        if (not isatty(0))\n        {\n            Buffer& buffer = *create_buffer_from_string(\n                \"*stdin*\", Buffer::Flags::NoHooks, read_fd(0));\n            apply_to_buffer(buffer);\n            write_buffer_to_fd(buffer, 1);\n            buffer_manager.delete_buffer(buffer);\n        }\n    }\n    catch (runtime_error& err)\n    {\n        write_stderr(format(\"error: {}\\n\", err.what()));\n    }\n\n    buffer_manager.clear_buffer_trash();\n    return 0;\n}\n\nint run_pipe(StringView session)\n{\n    try\n    {\n        send_command(session, read_fd(0));\n    }\n    catch (disconnected& e)\n    {\n        write_stderr(format(\"{}\\ndisconnecting\\n\", e.what()));\n        return -1;\n    }\n    return 0;\n}\n\nvoid signal_handler(int signal)\n{\n    TerminalUI::restore_terminal();\n    const char* text = nullptr;\n    switch (signal)\n    {\n        case SIGSEGV: text = \"SIGSEGV\"; break;\n        case SIGFPE:  text = \"SIGFPE\";  break;\n        case SIGQUIT: text = \"SIGQUIT\"; break;\n        case SIGPIPE: text = \"SIGPIPE\"; break;\n    }\n    auto msg = format(\"Received {}, exiting.\\nPid: {}\\nCallstack:\\n{}\",\n                      text, getpid(), Backtrace{}.desc());\n    write_stderr(msg);\n    notify_fatal_error(msg);\n\n    if (Server::has_instance())\n        Server::instance().close_session();\n    if (BufferManager::has_instance())\n        BufferManager::instance().backup_modified_buffers();\n\n    if (signal == SIGSEGV)\n    {\n        // generate core dump\n        ::signal(SIGSEGV, SIG_DFL);\n        ::kill(getpid(), SIGSEGV);\n    }\n    else\n        abort();\n}\n\n}\n\n}\n\nint main(int argc, char* argv[])\n{\n    using namespace Kakoune;\n\n    setlocale(LC_ALL, \"\");\n\n    set_signal_handler(SIGSEGV, signal_handler);\n    set_signal_handler(SIGFPE,  signal_handler);\n    set_signal_handler(SIGQUIT, signal_handler);\n    set_signal_handler(SIGTERM, signal_handler);\n    set_signal_handler(SIGPIPE, [](int){});\n    set_signal_handler(SIGINT, [](int){});\n    set_signal_handler(SIGCHLD, [](int){});\n    set_signal_handler(SIGTTOU, SIG_IGN);\n\n    const ParameterDesc param_desc{\n        SwitchMap{ { \"c\", { ArgCompleter{},  \"connect to given session\" } },\n                   { \"C\", { ArgCompleter{},  \"connect to given session, create if it does not exist\" } },\n                   { \"e\", { ArgCompleter{},  \"execute argument on client initialisation\" } },\n                   { \"E\", { ArgCompleter{},  \"execute argument on server initialisation\" } },\n                   { \"n\", { {}, \"do not source kakrc files on startup\" } },\n                   { \"s\", { ArgCompleter{},  \"set session name\" } },\n                   { \"d\", { {}, \"run as a headless session (requires -s)\" } },\n                   { \"p\", { ArgCompleter{},  \"just send stdin as commands to the given session\" } },\n                   { \"f\", { ArgCompleter{},  \"filter: for each file, select the entire buffer and execute the given keys\" } },\n                   { \"i\", { ArgCompleter{}, \"backup the files on which a filter is applied using the given suffix\" } },\n                   { \"q\", { {}, \"in filter mode, be quiet about errors applying keys\" } },\n                   { \"ui\", { ArgCompleter{}, \"set the type of user interface to use (terminal, dummy, or json)\" } },\n                   { \"l\", { {}, \"list existing sessions\" } },\n                   { \"clear\", { {}, \"clear dead sessions\" } },\n                   { \"debug\", { ArgCompleter{}, \"initial debug option value\" } },\n                   { \"version\", { {}, \"display kakoune version and exit\" } },\n                   { \"ro\", { {}, \"readonly mode\" } },\n                   { \"help\", { {}, \"display a help message and quit\" } } }\n    };\n\n    try\n    {\n        auto show_usage = [&]() {\n            write_stdout(format(\"Usage: {} [options] [file]... [+<line>[:<col>]|+:]\\n\\n\"\n                    \"Options:\\n\"\n                    \"{}\\n\"\n                    \"Prefixing a positional argument with a plus (`+`) sign will place the\\n\"\n                    \"cursor at a given set of coordinates, or the end of the buffer if the plus\\n\"\n                    \"sign is followed only by a colon (`:`)\\n\",\n                    argv[0], generate_switches_doc(param_desc.switches)));\n            return 0;\n        };\n\n        const auto params = ArrayView<char*>{argv+1, argv + argc}\n                          | transform([](auto* s) { return String{s}; })\n                          | gather<Vector<String>>();\n\n        if (contains(params, \"--help\"_sv))\n            return show_usage();\n\n        ParametersParser parser{params, param_desc};\n\n        const bool show_help_message = (bool)parser.get_switch(\"help\");\n        if (show_help_message)\n            return show_usage();\n\n        if (parser.get_switch(\"version\"))\n        {\n            write_stdout(format(\"Kakoune {}\\n\", Kakoune::version));\n            return 0;\n        }\n\n        const bool list_sessions = (bool)parser.get_switch(\"l\");\n        const bool clear_sessions = (bool)parser.get_switch(\"clear\");\n        if (list_sessions or clear_sessions)\n        {\n            list_files(session_directory(), [&](StringView session, auto&) {\n                if (session.substr(0_byte, 1_byte) == \".\")\n                    return;\n\n                const bool valid = check_session(session);\n                if (list_sessions)\n                    write_stdout(format(\"{}{}\\n\", session, valid ? \"\" : \" (dead)\"));\n                if (not valid and clear_sessions)\n                    unlink(session_path(session).c_str());\n            });\n            return 0;\n        }\n\n        if (auto session = parser.get_switch(\"p\"))\n        {\n            for (auto opt : { \"c\", \"n\", \"s\", \"d\", \"e\", \"E\", \"ro\" })\n            {\n                if (parser.get_switch(opt))\n                {\n                    write_stderr(format(\"error: -{} is incompatible with -p\\n\", opt));\n                    return -1;\n                }\n            }\n            return run_pipe(*session);\n        }\n\n        auto client_init = parser.get_switch(\"e\").value_or(StringView{});\n        auto server_init = parser.get_switch(\"E\").value_or(StringView{});\n        const UIType ui_type = parse_ui_type(parser.get_switch(\"ui\").value_or(\"terminal\"));\n\n        if (auto keys = parser.get_switch(\"f\"))\n        {\n            if (parser.get_switch(\"ro\"))\n            {\n                write_stderr(\"error: -ro is incompatible with -f\\n\");\n                return -1;\n            }\n\n            Vector<StringView> files;\n            for (size_t i = 0; i < parser.positional_count(); ++i)\n                files.emplace_back(parser[i]);\n\n            return run_filter(*keys, files, (bool)parser.get_switch(\"q\"),\n                              parser.get_switch(\"i\").value_or(StringView{}));\n        }\n\n        Vector<StringView> files;\n        Optional<BufferCoord> init_coord;\n        for (auto& name : parser)\n        {\n            if (not name.empty() and name[0_byte] == '+')\n            {\n                if (name == \"+\" or name  == \"+:\")\n                {\n                    client_init = client_init + \"; exec gj\";\n                    continue;\n                }\n                auto colon = find(name, ':');\n                if (auto line = str_to_int_ifp({name.begin()+1, colon}))\n                {\n                    init_coord = std::max<BufferCoord>({0,0}, {\n                        *line - 1,\n                        colon != name.end() ?\n                            str_to_int_ifp({colon+1, name.end()}).value_or(1) - 1\n                          : 0\n                    });\n                    continue;\n                }\n            }\n\n            files.emplace_back(name);\n        }\n\n        if ((bool)parser.get_switch(\"c\") + (bool)parser.get_switch(\"C\") + (bool)parser.get_switch(\"s\") > 1)\n        {\n            write_stderr(\"error: -s, -c and -C are mutually exclusive\\n\");\n            return -1;\n        }\n        if (parser.get_switch(\"c\") or (parser.get_switch(\"C\") and check_session(*parser.get_switch(\"C\"))))\n        {\n            for (auto opt : { \"n\", \"d\", \"E\", \"ro\" })\n            {\n                if (parser.get_switch(opt))\n                {\n                    write_stderr(format(\"error: -{} incompatible with connecting to an existing session\\n\", opt));\n                    return -1;\n                }\n            }\n            StringView session = parser.get_switch(\"c\") ? *parser.get_switch(\"c\") : *parser.get_switch(\"C\");\n            String new_files;\n            for (auto name : files) {\n                new_files += format(\"edit '{}'\", escape(real_path(name), \"'\", '\\''));\n                if (init_coord) {\n                    new_files += format(\" {} {}\", init_coord->line + 1, init_coord->column + 1);\n                    init_coord.reset();\n                }\n                new_files += \";\";\n            }\n\n            return run_client(session, {}, new_files + client_init, init_coord, ui_type, false);\n        }\n        else\n        {\n            StringView session = parser.get_switch(\"C\").value_or(parser.get_switch(\"s\").value_or(StringView{}));\n            try\n            {\n                auto ignore_kakrc = (bool)parser.get_switch(\"n\");\n                auto flags = (ignore_kakrc                                ? ServerFlags::IgnoreKakrc : ServerFlags::None) |\n                             (parser.get_switch(\"d\")                      ? ServerFlags::Daemon      : ServerFlags::None) |\n                             (parser.get_switch(\"ro\")                     ? ServerFlags::ReadOnly    : ServerFlags::None) |\n                             ((argc == 1 or (ignore_kakrc and argc == 2))\n                              and isatty(0)                               ? ServerFlags::StartupInfo : ServerFlags::None);\n                auto debug_flags = option_from_string(Meta::Type<DebugFlags>{}, parser.get_switch(\"debug\").value_or(\"\"));\n                return run_server(session, server_init, client_init, files.empty() ? StringView{} : files[0], init_coord, flags, ui_type, debug_flags, files);\n            }\n            catch (convert_to_client_mode& convert)\n            {\n                return run_client(convert.session, convert.client_name,\n                                  format(\"try %^buffer '{}'; select '{}'^; echo converted to client only mode\",\n                                         escape(convert.buffer_name, \"'^\", '\\\\'), convert.selections), {}, ui_type, true);\n            }\n        }\n    }\n    catch (parameter_error& error)\n    {\n        write_stderr(format(\"Error while parsing parameters: {}\\n\"\n                            \"Valid switches:\\n\"\n                            \"{}\", error.what(),\n                            generate_switches_doc(param_desc.switches)));\n       return -1;\n    }\n    catch (Kakoune::exception& error)\n    {\n        write_stderr(format(\"Fatal error: {}\\n\", error.what()));\n        return -1;\n    }\n    catch (std::exception& error)\n    {\n        write_stderr(format(\"uncaught exception ({}):\\n{}\\n\", typeid(error).name(), error.what()));\n        return -1;\n    }\n    catch (...)\n    {\n        write_stderr(\"uncaught exception\");\n        return -1;\n    }\n    return 0;\n}\n\n#if defined(__ELF__)\n#ifdef __arm__\n# define PROGBITS \"%progbits\"\n#else\n# define PROGBITS \"@progbits\"\n#endif\nasm(\".pushsection \\\".debug_gdb_scripts\\\", \\\"MS\\\",\" PROGBITS \",1\" R\"(\n.byte 4\n.ascii \"kakoune-inline-gdb.py\\n\"\n.ascii \"import os.path\\n\"\n.ascii \"sys.path.insert(0, os.path.dirname(gdb.current_objfile().filename) + '/../share/kak/gdb/')\\n\"\n.ascii \"import gdb.printing\\n\"\n.ascii \"import kakoune\\n\"\n.ascii \"gdb.printing.register_pretty_printer(gdb.current_objfile(), kakoune.build_pretty_printer())\\n\\0\"\n.popsection\n)\");\n#endif\n"
  },
  {
    "path": "src/memory.cc",
    "content": "#include \"memory.hh\"\n\nnamespace Kakoune\n{\n\nMemoryStats memory_stats[(size_t)MemoryDomain::Count] = {};\n\n}\n"
  },
  {
    "path": "src/memory.hh",
    "content": "#ifndef memory_hh_INCLUDED\n#define memory_hh_INCLUDED\n\n#include <cstddef>\n#include <new>\n\n#include \"assert.hh\"\n#include \"meta.hh\"\n\nnamespace Kakoune\n{\n\nenum class MemoryDomain\n{\n    Undefined,\n    String,\n    SharedString,\n    BufferContent,\n    BufferMeta,\n    Options,\n    Highlight,\n    Regions,\n    Display,\n    Mapping,\n    Commands,\n    Hooks,\n    Aliases,\n    EnvVars,\n    Faces,\n    Values,\n    Registers,\n    Client,\n    WordDB,\n    Selections,\n    Remote,\n    Events,\n    Completion,\n    Regex,\n    Count\n};\n\ninline const char* domain_name(MemoryDomain domain)\n{\n    switch (domain)\n    {\n        case MemoryDomain::Undefined: return \"Undefined\";\n        case MemoryDomain::String: return \"String\";\n        case MemoryDomain::SharedString: return \"SharedString\";\n        case MemoryDomain::BufferContent: return \"BufferContent\";\n        case MemoryDomain::BufferMeta: return \"BufferMeta\";\n        case MemoryDomain::Options: return \"Options\";\n        case MemoryDomain::Highlight: return \"Highlight\";\n        case MemoryDomain::Regions: return \"Regions\";\n        case MemoryDomain::Display: return \"Display\";\n        case MemoryDomain::Mapping: return \"Mapping\";\n        case MemoryDomain::Commands: return \"Commands\";\n        case MemoryDomain::Hooks: return \"Hooks\";\n        case MemoryDomain::WordDB: return \"WordDB\";\n        case MemoryDomain::Aliases: return \"Aliases\";\n        case MemoryDomain::EnvVars: return \"EnvVars\";\n        case MemoryDomain::Faces: return \"Faces\";\n        case MemoryDomain::Values: return \"Values\";\n        case MemoryDomain::Registers: return \"Registers\";\n        case MemoryDomain::Client: return \"Client\";\n        case MemoryDomain::Selections: return \"Selections\";\n        case MemoryDomain::Remote: return \"Remote\";\n        case MemoryDomain::Events: return \"Events\";\n        case MemoryDomain::Completion: return \"Completion\";\n        case MemoryDomain::Regex: return \"Regex\";\n        case MemoryDomain::Count: break;\n    }\n    kak_assert(false);\n    return \"\";\n}\n\nstruct MemoryStats\n{\n    size_t allocated_bytes;\n    size_t allocation_count;\n    size_t total_allocation_count;\n};\n\nextern MemoryStats memory_stats[(size_t)MemoryDomain::Count];\n\ninline void on_alloc(MemoryDomain domain, size_t size)\n{\n    auto& stats = memory_stats[(int)domain];\n    stats.allocated_bytes += size;\n    ++stats.allocation_count;\n    ++stats.total_allocation_count;\n}\n\ninline void on_dealloc(MemoryDomain domain, size_t size)\n{\n    auto& stats = memory_stats[(int)domain];\n    kak_assert(stats.allocated_bytes >= size);\n    stats.allocated_bytes -= size;\n    --stats.allocation_count;\n}\n\ntemplate<typename T, MemoryDomain domain>\nstruct Allocator\n{\n    using value_type = T;\n\n    Allocator() = default;\n    template<typename U>\n    Allocator(const Allocator<U, domain>&) {}\n\n    template<typename U>\n    struct rebind { using other = Allocator<U, domain>; };\n\n    T* allocate(size_t n)\n    {\n        size_t size = sizeof(T) * n;\n        on_alloc(domain, size);\n        return reinterpret_cast<T*>(::operator new(size));\n    }\n\n    void deallocate(T* ptr, size_t n)\n    {\n        size_t size = sizeof(T) * n;\n        on_dealloc(domain, size);\n        ::operator delete(ptr);\n    }\n};\n\ntemplate<typename T1, MemoryDomain d1, typename T2, MemoryDomain d2>\nconstexpr bool operator==(const Allocator<T1, d1>&, const Allocator<T2, d2>&)\n{\n    return d1 == d2;\n}\n\nconstexpr MemoryDomain memory_domain(Meta::AnyType) { return MemoryDomain::Undefined; }\n\ntemplate<typename T>\nconstexpr decltype(T::Domain) memory_domain(Meta::Type<T>) { return T::Domain; }\n\ntemplate<MemoryDomain d>\nstruct UseMemoryDomain\n{\n    static constexpr MemoryDomain Domain = d;\n\n    [[gnu::always_inline]]\n    static void* operator new(size_t size)\n    {\n        on_alloc(Domain, size);\n        return ::operator new(size);\n    }\n\n    [[gnu::always_inline]]\n    static void* operator new[](size_t size)\n    {\n        on_alloc(Domain, size);\n        return ::operator new[](size);\n    }\n\n    [[gnu::always_inline]]\n    static void* operator new(size_t size, void* ptr)\n    {\n        return ::operator new(size, ptr);\n    }\n\n    [[gnu::always_inline]]\n    static void operator delete(void* ptr, size_t size)\n    {\n        on_dealloc(Domain, size);\n        ::operator delete(ptr);\n    }\n\n    [[gnu::always_inline]]\n    static void operator delete[](void* ptr, size_t size)\n    {\n        on_dealloc(Domain, size);\n        ::operator delete[](ptr);\n    }\n};\n\n}\n\n#endif // memory_hh_INCLUDED\n"
  },
  {
    "path": "src/meta.hh",
    "content": "#ifndef meta_hh_INCLUDED\n#define meta_hh_INCLUDED\n\nnamespace Kakoune::inline Meta\n{\n\nstruct AnyType{};\ntemplate<typename T> struct Type : AnyType {};\n\n}\n\n#endif // meta_hh_INCLUDED\n"
  },
  {
    "path": "src/normal.cc",
    "content": "#include \"normal.hh\"\n\n#include \"array_view.hh\"\n#include \"assert.hh\"\n#include \"buffer.hh\"\n#include \"buffer_manager.hh\"\n#include \"buffer_utils.hh\"\n#include \"changes.hh\"\n#include \"client.hh\"\n#include \"command_manager.hh\"\n#include \"context.hh\"\n#include \"coord.hh\"\n#include \"diff.hh\"\n#include \"enum.hh\"\n#include \"face_registry.hh\"\n#include \"file.hh\"\n#include \"flags.hh\"\n#include \"input_handler.hh\"\n#include \"keymap_manager.hh\"\n#include \"option_manager.hh\"\n#include \"option_types.hh\"\n#include \"ranges.hh\"\n#include \"regex.hh\"\n#include \"register_manager.hh\"\n#include \"selectors.hh\"\n#include \"shell_manager.hh\"\n#include \"string.hh\"\n#include \"units.hh\"\n#include \"user_interface.hh\"\n#include \"unit_tests.hh\"\n#include \"window.hh\"\n#include \"word_db.hh\"\n\nnamespace Kakoune\n{\n\nenum class SelectMode\n{\n    Replace,\n    Extend,\n    Append,\n};\n\nconstexpr auto enum_desc(Meta::Type<SelectMode>)\n{\n    return make_array<EnumDesc<SelectMode>>({\n        { SelectMode::Replace, \"replace\" },\n        { SelectMode::Extend, \"extend\" },\n        { SelectMode::Append, \"append\" },\n    });\n}\n\nvoid merge_selections(Selection& sel, const Selection& new_sel)\n{\n    const bool forward = sel.cursor() >= sel.anchor();\n    const bool new_forward = new_sel.cursor() > new_sel.anchor();\n    if (forward and new_forward)\n        sel.anchor() = std::min(sel.anchor(), new_sel.anchor());\n    const bool backward = sel.cursor() <= sel.anchor();\n    const bool new_backward = new_sel.cursor() < new_sel.anchor();\n    if (backward and new_backward)\n        sel.anchor() = std::max(sel.anchor(), new_sel.anchor());\n\n    sel.cursor() = new_sel.cursor();\n}\n\nUnitTest test_merge_selection{[] {\n    auto merge = [](Selection sel, const Selection& new_sel) {\n        merge_selections(sel, new_sel);\n        return sel;\n    };\n    kak_assert(merge({{0, 1}, {0, 2} }, {{0, 3}, {0, 4}}) == Selection{{0, 1}, {0, 4}});\n    kak_assert(merge({{0, 1}, {0, 2} }, {{0, 1}, {0, 2}}) == Selection{{0, 1}, {0, 2}});\n    kak_assert(merge({{0, 1}, {0, 2} }, {{0, 0}, {0, 0}}) == Selection{{0, 1}, {0, 0}});\n    kak_assert(merge({{0, 1}, {0, 2} }, {{0, 0}, {0, 3}}) == Selection{{0, 0}, {0, 3}});\n    kak_assert(merge({{0, 1}, {0, 3} }, {{0, 4}, {0, 2}}) == Selection{{0, 1}, {0, 2}});\n    kak_assert(merge({{0, 1}, {0, 2} }, {{0, 1}, {0, 1}}) == Selection{{0, 1}, {0, 1}});\n}};\n\ntemplate<typename T>\nvoid select(Context& context, SelectMode mode, T func)\n{\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    if (mode == SelectMode::Append)\n    {\n        auto& sel = selections.main();\n        if (auto res = func(context, sel))\n        {\n            if (res->captures().empty())\n                res->captures() = sel.captures();\n            selections.push_back(std::move(*res));\n            selections.set_main_index(selections.size() - 1);\n        }\n    }\n    else\n    {\n        auto main_index = selections.main_index();\n        size_t new_size = 0;\n        for (size_t i = 0; i < selections.size(); ++i)\n        {\n            auto& sel = selections[i];\n            auto res = func(context, sel);\n            if (not res)\n            {\n                if (i <= main_index and main_index != 0)\n                    --main_index;\n                continue;\n            }\n\n            if (mode == SelectMode::Extend)\n                merge_selections(sel, *res);\n            else\n            {\n                sel.anchor() = res->anchor();\n                sel.cursor() = res->cursor();\n            }\n            if (not res->captures().empty())\n                sel.captures() = std::move(res->captures());\n            if (i != new_size)\n                selections[new_size] = std::move(sel);\n            ++new_size;\n        }\n        if (new_size == 0)\n            throw no_selections_remaining{};\n\n        selections.set_main_index(main_index);\n        selections.remove_from(new_size);\n    }\n\n    selections.sort_and_merge_overlapping();\n    selections.check_invariant();\n}\n\ntemplate<SelectMode mode, Optional<Selection> (*func)(const Context&, const Selection&)>\nvoid select(Context& context, NormalParams)\n{\n    select(context, mode, func);\n}\n\ntemplate<typename Func>\nvoid select_and_set_last(Context& context, SelectMode mode, Func&& func)\n{\n    context.set_last_select(\n        [=](Context& context){ select(context, mode, func); });\n    return select(context, mode, func);\n}\n\ntemplate<SelectMode mode = SelectMode::Replace>\nvoid select_coord(Context& context, BufferCoord coord)\n{\n    Buffer& buffer = context.buffer();\n    ScopedSelectionEdition selection_edition{context};\n    SelectionList& selections = context.selections();\n    coord = buffer.clamp(coord);\n    if (mode == SelectMode::Replace)\n        selections = SelectionList{ buffer, coord };\n    else if (mode == SelectMode::Extend)\n    {\n        for (auto& sel : selections)\n            sel.cursor() = coord;\n        selections.sort_and_merge_overlapping();\n    }\n}\n\ntemplate<InsertMode mode>\nvoid enter_insert_mode(Context& context, NormalParams params)\n{\n    context.input_handler().insert(mode, params.count);\n}\n\nvoid repeat_last_insert(Context& context, NormalParams)\n{\n    context.input_handler().repeat_last_insert();\n}\n\nvoid repeat_last_select(Context& context, NormalParams)\n{\n    context.repeat_last_select();\n}\n\nString build_autoinfo_for_mapping(const Context& context, KeymapMode mode,\n                                  ConstArrayView<KeyInfo> built_ins)\n{\n    auto& keymaps = context.keymaps();\n\n    Vector<std::pair<String, StringView>> descs;\n    for (auto& built_in : built_ins)\n    {\n        String keys = join(built_in.keys |\n                           filter([&](Key k){ return not keymaps.is_mapped(k, mode); }) |\n                           transform((String(*)(Key))to_string),\n                           ',', false);\n        if (not keys.empty())\n            descs.emplace_back(std::move(keys), built_in.docstring);\n    }\n\n    for (auto& key : keymaps.get_mapped_keys(mode))\n    {\n        const String& docstring = keymaps.get_mapping_docstring(key, mode);\n        if (keymaps.get_mapping_keys(key, mode).empty() and docstring.empty())\n            continue;\n        if (auto it = find_if(descs, [&](auto& elem) { return elem.second == docstring; });\n            it != descs.end())\n            it->first += ',' + to_string(key);\n        else\n            descs.emplace_back(to_string(key), docstring);\n    }\n\n    auto max_len = 0_col;\n    for (auto& [keys, docstring] : descs)\n    {\n        auto len = keys.column_length();\n        if (len > max_len)\n            max_len = len;\n    }\n\n    String res;\n    for (auto& [keys, docstring] : descs)\n        res += format(\"{}:{}{}\\n\",\n                      keys,\n                      String{' ', max_len - keys.column_length() + 1},\n                      docstring);\n    return res;\n}\n\ntemplate<SelectMode mode>\nvoid goto_commands(Context& context, NormalParams params)\n{\n    if (params.count != 0)\n    {\n        context.push_jump();\n        select_coord<mode>(context, LineCount{params.count - 1});\n        if (context.has_window())\n            context.window().center_line(LineCount{params.count-1});\n    }\n    else\n    {\n        on_next_key_with_autoinfo(context, \"goto\", KeymapMode::Goto,\n                                  [params](Key key, Context& context) {\n            auto cp = key.codepoint();\n            if (not cp or key == Key::Escape)\n                return;\n            auto& buffer = context.buffer();\n            switch (auto lower_cp = to_lower(*cp); lower_cp)\n            {\n            case 'g':\n            case 'k':\n                context.push_jump();\n                select_coord<mode>(context, BufferCoord{0,0});\n                break;\n            case 'l':\n                select<mode, select_to_line_end<true>>(context, {});\n                break;\n            case 'h':\n                select<mode, select_to_line_begin<true>>(context, {});\n                break;\n            case 'i':\n                select<mode, select_to_first_non_blank>(context, {});\n                break;\n            case 'j':\n                context.push_jump();\n                select_coord<mode>(context, buffer.line_count() - 1);\n                break;\n            case 'e':\n                context.push_jump();\n                select_coord<mode>(context, buffer.back_coord());\n                break;\n            case 't':\n                if (context.has_window())\n                {\n                    auto line = context.window().position().line;\n                    select_coord<mode>(context, line);\n                }\n                break;\n            case 'b':\n                if (context.has_window())\n                {\n                    auto& window = context.window();\n                    auto line = window.position().line + window.dimensions().line - 1;\n                    select_coord<mode>(context, line);\n                }\n                break;\n            case 'c':\n                if (context.has_window())\n                {\n                    auto& window = context.window();\n                    auto line = window.position().line + window.dimensions().line / 2;\n                    select_coord<mode>(context, line);\n                }\n                break;\n            case 'a':\n            {\n                Buffer* target = context.last_buffer();\n                if (not target)\n                {\n                    throw runtime_error(\"no last buffer\");\n                }\n                context.push_jump();\n                context.change_buffer(*target);\n                break;\n            }\n            case 'd':\n            case 'u':\n            {\n                auto offset = (lower_cp == 'd' ? 1_line : -1_line) * std::max(params.count, 1);\n                select(context, mode, [&](Context& context, Selection& sel) -> Optional<Selection> {\n                    if (context.has_window())\n                    {\n                        auto& cursor = sel.cursor();\n                        auto& window = context.window();\n                        if (auto display_coord = window.display_coord(cursor))\n                        {\n                            if (cursor.display_target == -1)\n                                cursor.display_target = display_coord->column;\n                            display_coord->column = cursor.display_target;\n                            if (auto buffer_coord = window.buffer_coord(*display_coord + offset))\n                                return Selection{BufferCoordAndTarget{*buffer_coord, -1, cursor.display_target}};\n                        }\n                    }\n                    const ColumnCount tabstop = context.options()[\"tabstop\"].get<int>();\n                    return Selection{context.buffer().offset_coord(sel.cursor(), offset, tabstop)};\n                });\n                break;\n            }\n\n            case 'f':\n            {\n                static constexpr char forbidden[] = { '\\'', '\\\\', '\\0' };\n                const auto& paths_opt = context.options()[\"path\"].get<Vector<String, MemoryDomain::Options>>();\n                const auto paths = context.selections() | transform([&](const auto& sel) {\n                    auto filename = content(buffer, sel);\n                    if (any_of(filename, [](char c){ return contains(forbidden, c); }))\n                        throw runtime_error(format(\"filename contains invalid characters: '{}'\", filename));\n\n                    const StringView buffer_dir = split_path(buffer.filename()).first;\n                    String path = find_file(filename, buffer_dir, paths_opt);\n                    if (path.empty())\n                        throw runtime_error(format(\"unable to find file '{}'\", filename));\n\n                    return path;\n                });\n\n                Buffer* buffer_main = nullptr;\n                for (auto&& [i, path] : paths | enumerate()) {\n                    Buffer* buffer = BufferManager::instance().get_buffer_ifp(path);\n                    if (not buffer)\n                    {\n                        buffer = open_file_buffer(path, context.hooks_disabled() ?\n                                                          Buffer::Flags::NoHooks\n                                                        : Buffer::Flags::None);\n                        buffer->flags() &= ~Buffer::Flags::NoHooks;\n                    }\n\n                    if (i == context.selections().main_index())\n                        buffer_main = buffer;\n                }\n                if (buffer_main and buffer_main != &context.buffer())\n                {\n                    context.push_jump();\n                    context.change_buffer(*buffer_main);\n                }\n                break;\n            }\n            case '.':\n            {\n                context.push_jump();\n                auto pos = buffer.last_modification_coord();\n                if (not pos)\n                    throw runtime_error(\"no last modification position\");\n                if (*pos >= buffer.back_coord())\n                    pos = buffer.back_coord();\n                select_coord<mode>(context, *pos);\n                break;\n            }\n            default:\n                throw runtime_error(\"key not mapped\");\n            }\n        }, (mode == SelectMode::Extend ? \"goto (extend to)\" : \"goto\"),\n        build_autoinfo_for_mapping(context, KeymapMode::Goto,\n            {{{'g','k'},\"buffer top\"},\n             {{'l'},    \"line end\"},\n             {{'h'},    \"line begin\"},\n             {{'i'},    \"line non blank start\"},\n             {{'j'},    \"buffer bottom\"},\n             {{'e'},    \"buffer end\"},\n             {{'t'},    \"window top\"},\n             {{'b'},    \"window bottom\"},\n             {{'c'},    \"window center\"},\n             {{'a'},    \"last buffer\"},\n             {{'f'},    \"file\"},\n             {{'.'},    \"last buffer change\"}}));\n    }\n}\n\ntemplate<bool lock>\nvoid view_commands(Context& context, NormalParams params)\n{\n    const int count = params.count;\n    on_next_key_with_autoinfo(context, \"view\", KeymapMode::View,\n                             [count](Key key, Context& context) {\n        context.ensure_cursor_visible = false;\n        if (key == Key::Escape)\n            return;\n\n        if (lock)\n            view_commands<true>(context, { count, 0 });\n\n        auto cp = key.codepoint();\n        if (not cp or not context.has_window())\n            return;\n\n        const BufferCoord cursor = context.selections().main().cursor();\n        Window& window = context.window();\n        window.update_display_buffer(context);\n        if (context.has_client())\n            context.client().force_redraw(false);\n\n        const DisplayCoord scrolloff = context.options()[\"scrolloff\"].get<DisplayCoord>();\n        const LineCount line_offset{std::min((window.dimensions().line - 1) / 2, scrolloff.line)};\n        const ColumnCount column_offset{std::min((window.dimensions().column - 1) / 2, scrolloff.column)};\n        switch (*cp)\n        {\n        case 'v':\n        case 'c':\n            window.center_line(cursor.line);\n            break;\n        case 'm':\n            window.center_column(\n                context.buffer()[cursor.line].column_count_to(cursor.column));\n            break;\n        case 't':\n            window.display_line_at(cursor.line, line_offset);\n            break;\n        case 'b':\n            window.display_line_at(cursor.line, window.dimensions().line-1-line_offset);\n            break;\n        case '<':\n            window.display_column_at(context.buffer()[cursor.line].column_count_to(cursor.column),\n                                    column_offset);\n            break;\n        case '>':\n            window.display_column_at(context.buffer()[cursor.line].column_count_to(cursor.column),\n                                     window.dimensions().column-1-column_offset);\n            break;\n        case 'h':\n            window.scroll(-std::max<ColumnCount>(1, count));\n            break;\n        case 'j':\n            scroll_window(context,  std::max<LineCount>(1, count), OnHiddenCursor::PreserveSelections);\n            break;\n        case 'k':\n            scroll_window(context, -std::max<LineCount>(1, count), OnHiddenCursor::PreserveSelections);\n            break;\n        case 'l':\n            window.scroll( std::max<ColumnCount>(1, count));\n            break;\n        default:\n            throw runtime_error(\"key not mapped\");\n        }\n    }, lock ? \"view (lock)\" : \"view\",\n    build_autoinfo_for_mapping(context, KeymapMode::View,\n        {{{'v','c'}, \"center cursor (vertically)\"},\n         {{'m'},     \"center cursor (horizontally)\"},\n         {{'t'},     \"cursor on top\"},\n         {{'b'},     \"cursor on bottom\"},\n         {{'<'},     \"cursor on left\"},\n         {{'>'},     \"cursor on right\"},\n         {{'h'},     \"scroll left\"},\n         {{'j'},     \"scroll down\"},\n         {{'k'},     \"scroll up\"},\n         {{'l'},     \"scroll right\"}}));\n}\n\nvoid replace_with_char(Context& context, NormalParams)\n{\n    on_next_key_with_autoinfo(context, \"replace-char\", KeymapMode::None,\n                             [](Key key, Context& context) {\n        auto cp = key.codepoint();\n        if (not cp or key == Key::Escape)\n            return;\n        ScopedEdition edition(context);\n        ScopedSelectionEdition selection_edition{context};\n        Buffer& buffer = context.buffer();\n        auto& sels = context.selections();\n        sels.merge_overlapping();\n        sels.for_each([&](size_t index, Selection& sel) {\n            CharCount count = char_length(buffer, sel);\n            replace(buffer, sel, String{*cp, count});\n        }, true);\n    }, \"replace with char\", \"enter char to replace with\\n\");\n}\n\nCodepoint swap_case(Codepoint cp)\n{\n    Codepoint res = to_lower(cp);\n    return res == cp ? to_upper(cp) : res;\n}\n\ntemplate<Codepoint (*func)(Codepoint)>\nvoid for_each_codepoint(Context& context, NormalParams)\n{\n    using Utf8It = utf8::iterator<BufferIterator>;\n\n    ScopedEdition edition(context);\n    ScopedSelectionEdition selection_edition{context};\n    Buffer& buffer = context.buffer();\n\n    context.selections().for_each([&](size_t index, Selection& sel) {\n        String str;\n        for (auto begin = Utf8It{buffer.iterator_at(sel.min()), buffer},\n                  end = Utf8It{buffer.iterator_at(sel.max()), buffer}+1;\n             begin != end; ++begin)\n            utf8::dump(std::back_inserter(str), func(*begin));\n        replace(buffer, sel, str);\n    }, false);\n}\n\nvoid command(const Context& context, EnvVarMap env_vars, char reg = 0)\n{\n    if (not CommandManager::has_instance())\n        throw runtime_error{\"commands are not supported\"};\n\n    String default_command = context.main_sel_register_value(reg ? reg : ':').str();\n\n    context.input_handler().prompt(\n        \":\", {}, default_command,\n        context.faces()[\"Prompt\"], PromptFlags::DropHistoryEntriesWithBlankPrefix,\n        ':',\n        [completer=CommandManager::Completer{}](const Context& context,\n           StringView cmd_line, ByteCount pos) mutable {\n               return completer(context, cmd_line, pos);\n        },\n        [env_vars = std::move(env_vars), default_command](StringView cmdline, PromptEvent event, Context& context) {\n            if (context.has_client())\n            {\n                context.client().info_hide();\n                if (event == PromptEvent::Change)\n                {\n                    auto info = CommandManager::instance().command_info(context, cmdline);\n                    context.input_handler().set_prompt_face(context.faces()[info or cmdline.empty() ? \"Prompt\" : \"Error\"]);\n\n                    auto autoinfo = context.options()[\"autoinfo\"].get<AutoInfo>();\n                    if (autoinfo & AutoInfo::Command)\n                    {\n                        if (cmdline.length() == 1 and is_horizontal_blank(cmdline[0_byte]))\n                            context.client().info_show(\"prompt\",\n                                                       \"commands preceded by a blank wont be saved to history\",\n                                                       {}, InfoStyle::Prompt);\n                        else if (info and not info->info.empty())\n                            context.client().info_show(info->name, info->info, {}, InfoStyle::Prompt);\n                    }\n                }\n            }\n            if (event == PromptEvent::Validate)\n            {\n                if (cmdline.empty())\n                    cmdline = default_command;\n\n                CommandManager::instance().execute(cmdline, context, { {}, env_vars });\n            }\n        });\n}\n\nvoid command(Context& context, NormalParams params)\n{\n    EnvVarMap env_vars = {\n        { \"count\", to_string(params.count) },\n        { \"register\", String{&params.reg, 1} }\n    };\n    command(context, std::move(env_vars), params.reg);\n}\n\nBufferRange apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> lines_before, StringView after)\n{\n    BufferCoord first = pos;\n    const auto lines_after = after | split_after<StringView>('\\n') | gather<Vector<StringView>>();\n\n    auto byte_count = [](auto&& lines, int first, int count) {\n        return std::accumulate(lines.begin() + first, lines.begin() + first + count, 0_byte,\n                               [](ByteCount l, StringView s) { return l + s.length(); });\n    };\n\n    bool tried_to_erase_final_newline = false;\n    for_each_diff(lines_before.begin(), (int)lines_before.size(),\n                  lines_after.begin(), (int)lines_after.size(),\n                  [&, posA = 0, posB = 0](DiffOp op, int len) mutable {\n        switch (op)\n        {\n        case DiffOp::Keep:\n            kak_assert(not tried_to_erase_final_newline);\n            pos = buffer.advance(pos, byte_count(lines_before, posA, len));\n            posA += len;\n            posB += len;\n            break;\n        case DiffOp::Add:\n            if (buffer.is_end(pos))\n                tried_to_erase_final_newline = false;\n            pos = buffer.insert(pos, {lines_after[posB].begin(),\n                                      lines_after[posB + len - 1].end()}).end;\n            posB += len;\n            break;\n        case DiffOp::Remove:\n        {\n            kak_assert(not tried_to_erase_final_newline);\n            BufferCoord end = buffer.advance(pos, byte_count(lines_before, posA, len));\n            tried_to_erase_final_newline |= buffer.is_end(end);\n            pos = buffer.erase(pos, end);\n            posA += len;\n            break;\n        }\n        }\n    });\n    if (tried_to_erase_final_newline)\n    {\n        first = std::min(first, buffer.back_coord());\n        pos = buffer.erase(buffer.back_coord(), buffer.end_coord());\n    }\n    return {first, pos};\n}\n\nUnitTest test_apply_diff{[] {\n    using Change = Buffer::Change;\n    auto validate = [&](LineCount line,\n                        StringView new_text,\n                        BufferRange expected_new_range,\n                        ConstArrayView<Change> expected_buffer_changes)\n    {\n        Buffer buffer{\"\", Buffer::Flags::None, {\n            StringData::create(\"line1\\n\"),\n            StringData::create(\"line2\\n\"),\n            StringData::create(\"line3\\n\"),\n        }};\n        const size_t timestamp = buffer.timestamp();\n        Vector<StringView> old_text;\n        for (auto i = line; i < buffer.line_count(); i++)\n            old_text.push_back(buffer[i]);\n        BufferRange new_text_range = apply_diff(buffer, BufferCoord{line, 0}, old_text, new_text);\n        kak_assert(new_text_range == expected_new_range);\n        kak_assert(buffer.changes_since(timestamp) == expected_buffer_changes);\n    };\n    // When appending at end, we add any missing newline\n    validate(\n        /*line=*/3,\n        \"added-line3-missing-eol\",\n        BufferRange{{3, 0}, {4, 0}},\n        {\n            Change{Change::Insert, {3, 0}, {4, 0}},\n        }\n    );\n    // Special case: erasing until buffer end also erases the final newline.\n    validate(\n        /*line=*/2,\n        \"\",\n        BufferRange{{1, 5}, {1, 5}},\n        {\n            Change{Change::Erase, {2, 0}, {3, 0}},\n        }\n    );\n    // Erasing and appending at end still produces forward-only changes.\n    validate(\n        /*line=*/2,\n        \"changed-line3\\n\"\n        \"added-line4\\n\"\n        \"added-line5\\n\",\n        BufferRange{{2, 0}, {5, 0}},\n        {\n            Change{Change::Erase,  {2, 0}, {3, 0}},\n            Change{Change::Insert, {2, 0}, {5, 0}},\n        }\n    );\n    // Same result when append is missing newline.\n    validate(\n        /*line=*/2,\n        \"changed-line3\\n\"\n        \"added-line4\\n\"\n        \"added-line5-missing-eol\",\n        BufferRange{{2, 0}, {5, 0}},\n        {\n            Change{Change::Erase,  {2, 0}, {3, 0}},\n            Change{Change::Insert, {2, 0}, {5, 0}},\n        }\n    );\n}};\n\ntemplate<bool replace>\nvoid pipe(Context& context, NormalParams params)\n{\n    const char* prompt = replace ? \"pipe:\" : \"pipe-to:\";\n    String default_command = context.main_sel_register_value(params.reg ? params.reg : '|').str();\n\n    context.input_handler().prompt(\n        prompt, {}, default_command, context.faces()[\"Prompt\"],\n        PromptFlags::DropHistoryEntriesWithBlankPrefix, '|',\n        shell_complete,\n        [default_command, selection_edition=ScopedSelectionEdition(context)]\n        (StringView cmdline, PromptEvent event, Context& context)\n        {\n            if (event != PromptEvent::Validate)\n                return;\n\n            if (cmdline.empty())\n                cmdline = default_command;\n\n            if (cmdline.empty())\n                return;\n\n            Buffer& buffer = context.buffer();\n            if (replace)\n            {\n                buffer.throw_if_read_only();\n                ScopedEdition edition(context);\n                ForwardChangesTracker changes_tracker;\n                size_t timestamp = buffer.timestamp();\n                SelectionList selections = context.selections();\n                for (auto& sel : selections)\n                {\n                    const auto first = changes_tracker.get_new_coord_tolerant(sel.min());\n                    const auto last = changes_tracker.get_new_coord_tolerant(sel.max());\n\n                    Vector<StringDataPtr> keep_alive;\n                    Vector<StringView> in_lines;\n                    for (auto line = first.line; line <= last.line; ++line)\n                    {\n                        auto& storage = buffer.line_storage(line);\n                        keep_alive.push_back(storage);\n                        auto content = storage->strview();;\n                        if (line == last.line)\n                            content = content.substr(0, last.column + utf8::codepoint_size(content[last.column]));\n                        if (line == first.line)\n                            content = content.substr(first.column);\n                        in_lines.push_back(content);\n                    }\n\n                    // Needed in case we read selections inside the cmdline\n                    context.selections_write_only().set({keep_direction(Selection{first, last}, sel)}, 0);\n\n                    String out = ShellManager::instance().eval(\n                        cmdline, context,\n                        [it = in_lines.begin(), end = in_lines.end()]() mutable {\n                            return (it != end) ? *it++ : StringView{};\n                        }, ShellManager::Flags::WaitForStdout).first;\n\n                    if (in_lines.back().back() != '\\n' and not out.empty() and out.back() == '\\n')\n                        out.resize(out.length()-1, 0);\n\n                    auto [new_first, new_end] = apply_diff(buffer, first, in_lines, out);\n                    if (new_first != new_end)\n                    {\n                        auto& min = sel.min();\n                        auto& max = sel.max();\n                        min = new_first;\n                        max = buffer.char_prev(new_end);\n                    }\n                    else\n                    {\n                        if (new_end != BufferCoord{})\n                            new_end = buffer.char_prev(new_end);\n                        sel.set(new_end);\n                    }\n\n                    changes_tracker.update(buffer, timestamp);\n                }\n                selections.force_timestamp(timestamp);\n                context.selections_write_only() = std::move(selections);\n            }\n            else\n            {\n                SelectionList& selections = context.selections();\n                const auto old_main = selections.main_index();\n                for (int i = 0; i < selections.size(); ++i)\n                {\n                    selections.set_main_index(i);\n                    ShellManager::instance().eval(cmdline, context,\n                                                  content(buffer, selections.main()),\n                                                  ShellManager::Flags::None);\n                }\n                selections.set_main_index(old_main);\n            }\n        });\n}\n\nvoid yank(Context& context, NormalParams params)\n{\n    const char reg = params.reg ? params.reg : '\"';\n    RegisterManager::instance()[reg].set(context, context.selections_content());\n    context.print_status({ format(\"yanked {} selections to register {}\",\n                                  context.selections().size(), reg),\n                           context.faces()[\"Information\"] });\n}\n\ntemplate<bool yank>\nvoid erase_selections(Context& context, NormalParams params)\n{\n    if (yank)\n    {\n        const char reg = params.reg ? params.reg : '\"';\n        RegisterManager::instance()[reg].set(context, context.selections_content());\n    }\n    ScopedEdition edition(context);\n    ScopedSelectionEdition selection_edition{context};\n    context.selections().erase();\n}\n\ntemplate<bool yank>\nvoid change(Context& context, NormalParams params)\n{\n    if (yank)\n    {\n        const char reg = params.reg ? params.reg : '\"';\n        RegisterManager::instance()[reg].set(context, context.selections_content());\n    }\n    enter_insert_mode<InsertMode::Replace>(context, params);\n}\n\nBufferCoord paste_pos(Buffer& buffer, BufferCoord min, BufferCoord max, PasteMode mode, bool linewise)\n{\n    switch (mode)\n    {\n        case PasteMode::Append:\n            if (buffer.is_end(max))\n                return max;\n            return linewise ? std::min(buffer.line_count(), max.line+1) : buffer.char_next(max);\n        case PasteMode::Insert:\n            return linewise ? min.line : min;\n        default:\n            kak_assert(false);\n            return {};\n    }\n}\n\ntemplate<PasteMode mode>\nvoid paste(Context& context, NormalParams params)\n{\n    const char reg = params.reg ? params.reg : '\"';\n    auto strings = RegisterManager::instance()[reg].get(context);\n    const bool linewise = all_of(strings, [](StringView str) {\n        return not str.empty() and str.back() == '\\n';\n    });\n\n    auto& buffer = context.buffer();\n    ScopedEdition edition(context);\n    ScopedSelectionEdition selection_edition{context};\n    context.selections().for_each([&, last=BufferCoord{}](size_t index, Selection& sel) mutable {\n        auto& str = strings[index % strings.size()];\n        auto& min = sel.min();\n        auto& max = sel.max();\n        BufferRange range = (mode == PasteMode::Replace) ?\n            buffer.replace(min, buffer.char_next(max), str)\n          : buffer.insert(paste_pos(buffer, min, std::max(max, last), mode, linewise), str);\n        min = range.begin;\n        max = range.end > range.begin ? buffer.char_prev(range.end) : range.begin;\n        last = max;\n    }, mode == PasteMode::Append);\n}\n\ntemplate<PasteMode mode>\nvoid paste_all(Context& context, NormalParams params)\n{\n    const char reg = params.reg ? params.reg : '\"';\n    auto strings = RegisterManager::instance()[reg].get(context);\n    String all;\n    Vector<std::pair<ByteCount, ByteCount>> offsets;\n    for (auto& str : strings)\n    {\n        if (str.empty())\n            continue;\n\n        all += str;\n        ByteCount cursor_offset = utf8::previous(str.end(), str.begin()) - str.begin();\n        offsets.emplace_back(cursor_offset, str.length());\n    }\n    const bool linewise = all_of(strings, [](StringView str) {\n        return not str.empty() and str.back() == '\\n';\n    });\n\n    if (offsets.empty())\n        throw runtime_error(\"nothing to paste\");\n\n    Buffer& buffer = context.buffer();\n    Vector<Selection> result;\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    {\n        ScopedEdition edition(context);\n        selections.for_each([&](size_t, const Selection& sel) {\n            auto range = (mode == PasteMode::Replace) ?\n                buffer.replace(sel.min(), buffer.char_next(sel.max()), all)\n              : buffer.insert(paste_pos(buffer, sel.min(), sel.max(), mode, linewise), all);\n\n            BufferCoord pos = range.begin;\n            for (auto [cursor_offset, length] : offsets)\n            {\n                BufferCoord cursor = buffer.advance(pos, cursor_offset);\n                result.emplace_back(pos, cursor);\n                pos = buffer.advance(cursor, length - cursor_offset);\n            }\n        }, mode == PasteMode::Append);\n    }\n    selections = std::move(result);\n}\n\ntemplate<PasteMode mode>\nvoid insert_output(Context& context, NormalParams params)\n{\n    const char* prompt = mode == PasteMode::Insert ? \"insert-output:\" : \"append-output:\";\n    String default_command = context.main_sel_register_value(params.reg ? params.reg : '|').str();\n\n    context.input_handler().prompt(\n        prompt, {}, default_command, context.faces()[\"Prompt\"],\n        PromptFlags::DropHistoryEntriesWithBlankPrefix, '|',\n        shell_complete,\n        [default_command, selection_edition=ScopedSelectionEdition(context)]\n        (StringView cmdline, PromptEvent event, Context& context)\n        {\n            if (event != PromptEvent::Validate)\n                return;\n\n            if (cmdline.empty())\n                cmdline = default_command;\n\n            if (cmdline.empty())\n                return;\n\n            ScopedEdition edition(context);\n            auto& selections = context.selections();\n            auto& buffer = context.buffer();\n            const size_t old_main = selections.main_index();\n\n            selections.for_each([&](size_t index, Selection& sel) {\n                selections.set_main_index(index);\n                auto [out, status] = ShellManager::instance().eval(\n                    cmdline, context, content(context.buffer(), sel),\n                    ShellManager::Flags::WaitForStdout);\n\n                auto& min = sel.min();\n                auto& max = sel.max();\n                auto range = insert(buffer, sel, paste_pos(buffer, min, max, mode, false), out);\n                min = range.begin;\n                max = range.end > range.begin ? buffer.char_prev(range.end) : range.begin;\n            }, mode == PasteMode::Append);\n            selections.set_main_index(old_main);\n        });\n}\n\nconstexpr RegexCompileFlags direction_flags(RegexMode mode)\n{\n    return (mode & RegexMode::Forward) ?\n        RegexCompileFlags::None : RegexCompileFlags::Backward | RegexCompileFlags::NoForward;\n}\n\ntemplate<typename Func>\nvoid regex_prompt(Context& context, String prompt, char reg, RegexMode mode, Func func)\n{\n    kak_assert(is_direction(mode));\n    DisplayCoord position = context.has_window() ? context.window().position() : DisplayCoord{};\n    SelectionList selections = context.selections();\n    auto default_regex = RegisterManager::instance()[reg].get_main(context, context.selections().main_index());\n    context.input_handler().prompt(\n        std::move(prompt), {}, default_regex, context.faces()[\"Prompt\"],\n        PromptFlags::Search, reg,\n        [](const Context& context, StringView regex, ByteCount pos) -> Completions {\n            auto current_word = [](StringView s) {\n                auto it = s.end();\n                while (it != s.begin() and is_word(*(it-1)))\n                    --it;\n                StringView res{it, s.end()};\n                if (it == s.begin() or res.empty())\n                    return res;\n\n                int backslashes = 0;\n                for (auto bs = it; bs != s.begin() && *(bs-1) == '\\\\'; --bs)\n                    ++backslashes;\n                return (backslashes % 2 == 1) ? res.substr(1_byte) : res;\n            };\n\n            const auto word = current_word(regex.substr(0_byte, pos));\n            auto matches = get_word_db(context.buffer()).find_matching(word);\n            constexpr size_t max_count = 100;\n            CandidateList candidates;\n            candidates.reserve(std::min(matches.size(), max_count));\n            for_n_best(matches, max_count, [](auto& lhs, auto& rhs) { return rhs < lhs; },\n                       [&](auto&& m) { candidates.push_back(m.candidate().str()); return true; });\n            return {(int)(word.begin() - regex.begin()), pos,  std::move(candidates) };\n        },\n        [=, func=std::move(func), saved_reg=RegisterManager::instance()[reg].save(context),\n         selection_edition=ScopedSelectionEdition(context)]\n        (StringView str, PromptEvent event, Context& context) mutable {\n            try\n            {\n                if (event != PromptEvent::Change and context.has_client())\n                    context.client().info_hide();\n\n                const bool incsearch = context.options()[\"incsearch\"].get<bool>();\n                if (incsearch)\n                {\n                    selections.update();\n                    context.selections_write_only() = selections;\n                    if (context.has_window())\n                        context.window().set_position(position);\n\n                    context.input_handler().set_prompt_face(context.faces()[\"Prompt\"]);\n                    RegisterManager::instance()[reg].restore(context, saved_reg);\n                }\n\n                switch (event)\n                {\n                case PromptEvent::Abort: return;\n                case PromptEvent::Change:\n                    if (not incsearch)\n                        return;\n                    if (not str.empty())\n                        RegisterManager::instance()[reg].set(context, str.str());\n                    break;\n                case PromptEvent::Validate:\n                    if (not str.empty())\n                        RegisterManager::instance()[reg].set(context, str.str());\n                    context.push_jump();\n                    break;\n                }\n                func(Regex{str.empty() ? default_regex : str, direction_flags(mode)}, event, context);\n            }\n            catch (regex_error& err)\n            {\n                if (event == PromptEvent::Validate)\n                    throw;\n                else\n                    context.input_handler().set_prompt_face(context.faces()[\"Error\"]);\n            }\n            catch (runtime_error&)\n            {\n                context.selections_write_only() = selections;\n                // only validation should propagate errors,\n                // incremental search should not.\n                if (event == PromptEvent::Validate)\n                    throw;\n            }\n        });\n}\n\nvoid select_next_matches(Context& context, const Regex& regex, RegexMode mode, int count)\n{\n     auto& selections = context.selections();\n     do {\n         bool wrapped = false;\n         for (auto& sel : selections)\n             sel = keep_direction(find_next_match(context, sel, regex, mode, wrapped), sel);\n         selections.sort_and_merge_overlapping();\n     } while (--count > 0);\n}\n\nvoid extend_to_next_matches(Context& context, const Regex& regex, RegexMode mode, int count)\n{\n     Vector<Selection> new_sels;\n     auto& selections = context.selections();\n     do {\n         bool wrapped = false;\n         size_t main_index = selections.main_index();\n         for (auto& sel : selections)\n         {\n             auto new_sel = find_next_match(context, sel, regex, mode, wrapped);\n             if (not wrapped)\n             {\n                 new_sels.push_back(sel);\n                 merge_selections(new_sels.back(), new_sel);\n             }\n             else if (new_sels.size() <= main_index and main_index != 0)\n                 --main_index;\n         }\n         if (new_sels.empty())\n             throw runtime_error{\"All selections wrapped\"};\n\n         selections.set(std::move(new_sels), main_index);\n         new_sels.clear();\n     } while (--count > 0);\n}\n\nvoid search(Context& context, NormalParams params, SelectMode mode, RegexMode regex_mode)\n{\n    kak_assert(is_direction(regex_mode));\n    const StringView prompt = mode == SelectMode::Extend ?\n        (regex_mode & RegexMode::Forward ? \"search (extend):\" : \"reverse search (extend):\")\n      : (regex_mode & RegexMode::Forward ? \"search:\"          : \"reverse search:\");\n\n    const char reg = to_lower(params.reg ? params.reg : '/');\n    const int count = params.count;\n\n    regex_prompt(context, prompt.str(), reg, regex_mode,\n                 [=](const Regex& regex, PromptEvent event, Context& context) {\n                     if (regex.empty() or regex.str().empty())\n                         return;\n\n                     if (mode == SelectMode::Extend)\n                         extend_to_next_matches(context, regex, regex_mode, count);\n                     else\n                         select_next_matches(context, regex, regex_mode, count);\n                 });\n}\n\nvoid search_next(Context& context, NormalParams params, SelectMode mode, RegexMode regex_mode)\n{\n    const char reg = to_lower(params.reg ? params.reg : '/');\n    StringView str = RegisterManager::instance()[reg].get(context).front();\n    if (not str.empty())\n    {\n        Regex regex{str, direction_flags(regex_mode)};\n        ScopedSelectionEdition selection_edition{context};\n        auto& selections = context.selections();\n        bool main_wrapped = false;\n        do {\n            bool wrapped = false;\n            if (mode == SelectMode::Replace)\n            {\n                auto& sel = selections.main();\n                sel = keep_direction(find_next_match(context, sel, regex, regex_mode, wrapped), sel);\n            }\n            else if (mode == SelectMode::Append)\n            {\n                auto sel = keep_direction(\n                    find_next_match(context, selections.main(), regex, regex_mode, wrapped),\n                    selections.main());\n                selections.push_back(std::move(sel));\n                selections.set_main_index(selections.size() - 1);\n            }\n            selections.sort_and_merge_overlapping();\n            main_wrapped = main_wrapped or wrapped;\n        } while (--params.count > 0);\n\n        if (main_wrapped)\n            context.print_status({\"main selection search wrapped around buffer\", context.faces()[\"Information\"]});\n    }\n    else\n        throw runtime_error(\"no search pattern\");\n}\n\ntemplate<bool smart>\nvoid use_selection_as_search_pattern(Context& context, NormalParams params)\n{\n    const auto& buffer = context.buffer();\n    auto patterns = context.selections() | transform([&](auto&& sel) {\n        const auto beg = sel.min(), end = buffer.char_next(sel.max());\n        return format(\"{}{}{}\",\n                      smart and is_bow(buffer, beg) ? \"\\\\b\" : \"\",\n                      escape(buffer.string(beg, end), \"^$\\\\.*+?()[]{}|\", '\\\\'),\n                      smart and is_eow(buffer, end) ? \"\\\\b\" : \"\");\n    }) | gather<HashSet<String>>();\n    String pattern = join(patterns, '|', false);\n\n    const char reg = to_lower(params.reg ? params.reg : '/');\n\n    context.print_status({\n        format(\"register '{}' set to '{}'\", reg, pattern),\n        context.faces()[\"Information\"] });\n\n    RegisterManager::instance()[reg].set(context, {pattern});\n\n    // Hack, as Window do not take register state into account\n    if (context.has_client())\n        context.client().force_redraw();\n}\n\nvoid select_regex(Context& context, NormalParams params)\n{\n    const char reg = to_lower(params.reg ? params.reg : '/');\n    const int capture = params.count;\n    auto prompt = capture ? format(\"select (capture {}):\", capture) :  \"select:\"_str;\n\n    regex_prompt(context, std::move(prompt), reg, RegexMode::Forward,\n                 [capture](Regex ex, PromptEvent event, Context& context) {\n        auto& selections = context.selections();\n        auto& buffer = selections.buffer();\n        if (not ex.empty() and not ex.str().empty())\n            selections = SelectionList{buffer, select_matches(buffer, selections, ex, capture)};\n    });\n}\n\nvoid split_regex(Context& context, NormalParams params)\n{\n    const char reg = to_lower(params.reg ? params.reg : '/');\n    const int capture = params.count;\n    auto prompt = capture ? format(\"split (on capture {}):\", (int)capture) :  \"split:\"_str;\n\n    regex_prompt(context, std::move(prompt), reg, RegexMode::Forward,\n                 [capture](Regex ex, PromptEvent event, Context& context) {\n        auto& selections = context.selections();\n        auto& buffer = selections.buffer();\n        if (not ex.empty() and not ex.str().empty())\n            selections = SelectionList{buffer, split_on_matches(buffer, selections, ex, capture)};\n    });\n}\n\nvoid split_lines(Context& context, NormalParams params)\n{\n    const LineCount count{params.count == 0 ? 1 : params.count};\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    auto& buffer = context.buffer();\n    Vector<Selection> res;\n    for (auto& sel : selections)\n    {\n        if (sel.anchor().line == sel.cursor().line)\n        {\n             res.push_back(std::move(sel));\n             continue;\n        }\n        auto min = sel.min();\n        auto max = sel.max();\n        for (auto line = min.line; line <= max.line; line += count)\n        {\n            auto last_line = std::min(line + count - 1, buffer.line_count() - 1);\n            res.push_back(keep_direction({\n                    std::max<BufferCoord>(min, line),\n                    std::min<BufferCoord>(max, {last_line, buffer[last_line].length() - 1})\n                }, sel));\n        }\n    }\n    selections = std::move(res);\n}\n\nvoid select_boundaries(Context& context, NormalParams)\n{\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    Vector<Selection> res;\n    for (auto& sel : selections)\n    {\n        res.push_back(sel.min());\n        if (sel.min() != sel.max())\n            res.push_back(sel.max());\n    }\n    selections = std::move(res);\n}\n\nvoid join_lines_select_spaces(Context& context, NormalParams)\n{\n    auto& buffer = context.buffer();\n    Vector<Selection> selections;\n    ScopedSelectionEdition selection_edition{context};\n    for (auto& sel : context.selections())\n    {\n        const LineCount min_line = sel.min().line;\n        const LineCount max_line = sel.max().line;\n        auto end_line = std::min(buffer.line_count()-1,\n                                 max_line + (min_line == max_line ? 1 : 0));\n        for (LineCount line = min_line; line < end_line; ++line)\n        {\n            auto begin = buffer.iterator_at({line, buffer[line].length()-1});\n            auto end = std::find_if_not(begin+1, buffer.end(), is_horizontal_blank);\n            selections.emplace_back(begin.coord(), (end-1).coord());\n        }\n    }\n    if (selections.empty())\n        return;\n    context.selections_write_only() = std::move(selections);\n    context.selections().merge_consecutive();\n    ScopedEdition edition(context);\n    context.selections().replace({\" \"_str});\n}\n\nvoid join_lines(Context& context, NormalParams params)\n{\n    ScopedSelectionEdition selection_edition{context};\n    SelectionList sels{context.selections()};\n    auto restore_sels = OnScopeEnd([&]{\n        sels.update();\n        context.selections_write_only() = std::move(sels);\n    });\n\n    join_lines_select_spaces(context, params);\n}\n\ntemplate<bool matching>\nvoid keep(Context& context, NormalParams params)\n{\n    constexpr StringView prompt = matching ? \"keep matching:\" : \"keep not matching:\";\n\n    const char reg = to_lower(params.reg ? params.reg : '/');\n\n    regex_prompt(context, prompt.str(), reg, RegexMode::Forward,\n                 [selection_edition=ScopedSelectionEdition(context)]\n                 (const Regex& regex, PromptEvent event, Context& context) {\n        if (regex.empty() or regex.str().empty())\n            return;\n\n        const Buffer& buffer = context.buffer();\n        Vector<Selection> keep;\n        for (auto& sel : context.selections())\n        {\n            auto begin = buffer.iterator_at(sel.min());\n            auto end = utf8::next(buffer.iterator_at(sel.max()), buffer.end());\n            // We do not consider if end is on an eol, as it seems to\n            // give more intuitive behaviours in keep use cases.\n            const auto flags = match_flags(is_bol(begin.coord()), false,\n                                           is_bow(buffer, begin.coord()),\n                                           is_eow(buffer, end.coord()));\n            if (regex_search(begin, end, begin, end, regex, flags, EventManager::handle_urgent_events) == matching)\n                keep.push_back(sel);\n        }\n        if (keep.empty())\n            throw no_selections_remaining{};\n        context.selections_write_only() = std::move(keep);\n    });\n}\n\nvoid keep_pipe(Context& context, NormalParams params)\n{\n    String default_command = context.main_sel_register_value(params.reg ? params.reg : '|').str();\n    context.input_handler().prompt(\n        \"keep pipe:\", {}, default_command, context.faces()[\"Prompt\"],\n        PromptFlags::DropHistoryEntriesWithBlankPrefix, '|', shell_complete,\n        [default_command, selection_edition=ScopedSelectionEdition(context)]\n        (StringView cmdline, PromptEvent event, Context& context) {\n            if (event != PromptEvent::Validate)\n                return;\n\n            if (cmdline.empty())\n                cmdline = default_command;\n\n            if (cmdline.empty())\n                return;\n\n            const Buffer& buffer = context.buffer();\n            auto& shell_manager = ShellManager::instance();\n            Vector<Selection> keep;\n\n            auto& selections = context.selections();\n            const size_t old_main = selections.main_index();\n            size_t new_main = -1;\n            for (int i = 0; i < selections.size(); ++i)\n            {\n                auto& sel = selections[i];\n                selections.set_main_index(i);\n                if (shell_manager.eval(cmdline, context, content(buffer, sel),\n                                       ShellManager::Flags::None).second == 0)\n                {\n                    keep.push_back(sel);\n                    if (i >= old_main and new_main == (size_t)-1)\n                        new_main = keep.size() - 1;\n                }\n            }\n            if (keep.empty())\n                throw no_selections_remaining{};\n            if (new_main == -1)\n                new_main = keep.size() - 1;\n            context.selections_write_only().set(std::move(keep), new_main);\n    });\n}\ntemplate<bool indent_empty = false>\nvoid indent(Context& context, NormalParams params)\n{\n    CharCount count = params.count ? params.count : 1;\n    CharCount indent_width = context.options()[\"indentwidth\"].get<int>();\n    String indent = indent_width == 0 ? String{'\\t', count} : String{' ', indent_width * count};\n\n    ScopedEdition edition(context);\n    auto& buffer = context.buffer();\n    LineCount last_line = 0;\n    ScopedSelectionEdition selection_edition{context};\n    for (auto& sel : context.selections())\n    {\n        for (auto line = std::max(last_line, sel.min().line); line < sel.max().line+1; ++line)\n        {\n            if (indent_empty or buffer[line].length() > 1)\n                buffer.insert(line, indent);\n        }\n        // avoid reindenting the same line if multiple selections are on it\n        last_line = sel.max().line+1;\n    }\n}\n\ntemplate<bool deindent_incomplete = true>\nvoid deindent(Context& context, NormalParams params)\n{\n    ColumnCount count = params.count ? params.count : 1;\n    ColumnCount tabstop = context.options()[\"tabstop\"].get<int>();\n    ColumnCount indent_width = context.options()[\"indentwidth\"].get<int>();\n    if (indent_width == 0)\n        indent_width = tabstop;\n    indent_width = indent_width * count;\n\n    auto& buffer = context.buffer();\n    LineCount last_line = 0;\n    ScopedSelectionEdition selection_edition{context};\n    for (auto& sel : context.selections())\n    {\n        for (auto line = std::max(sel.min().line, last_line);\n             line < sel.max().line+1; ++line)\n        {\n            ColumnCount width = 0;\n            auto content = buffer[line];\n            for (auto column = 0_byte; column < content.length(); ++column)\n            {\n                const char c = content[column];\n                if (c == '\\t')\n                    width = (width / tabstop + 1) * tabstop;\n                else if (c == ' ')\n                    ++width;\n                else\n                {\n                    if (deindent_incomplete and width != 0)\n                        buffer.erase(line, BufferCoord{line, column});\n                    break;\n                }\n                if (width >= indent_width)\n                {\n                    buffer.erase(line, BufferCoord{line, column+1});\n                    break;\n                }\n            }\n        }\n        // avoid reindenting the same line if multiple selections are on it\n        last_line = sel.max().line + 1;\n    }\n}\n\nvoid select_object(Context& context, NormalParams params, ObjectFlags flags, SelectMode mode = SelectMode::Replace)\n{\n    auto get_title = [&] {\n        const auto whole_flags = (ObjectFlags::ToBegin | ObjectFlags::ToEnd);\n        const bool whole = (flags & whole_flags) == whole_flags;\n        return format(\"{} {}{}{} object{}\",\n                      mode == SelectMode::Extend ? \"extend\" : \"select\",\n                      whole ? \"\" : \"to \",\n                      flags & ObjectFlags::Inner ? \"inner \" : \"\",\n                      flags & ObjectFlags::Nested ? \"nested\" : \"surronding\",\n                      whole ? \"\" : (flags & ObjectFlags::ToBegin ? \" begin\" : \" end\"));\n    };\n\n    on_next_key_with_autoinfo(context,\"text-object\",  KeymapMode::Object,\n                             [=](Key key, Context& context) {\n        if (key == Key::Escape)\n            return;\n\n        const bool nested = flags & ObjectFlags::Nested;\n        const int count = params.count <= 0 ? 0 : params.count - 1;\n        auto select_nested_and_set_last = [count, flags](Context& context, auto&& func) {\n            context.set_last_select(\n                [=](Context& context){\n                    ScopedSelectionEdition selection_edition{context};\n                    context.selections() = func(context, count, flags);\n                });\n            ScopedSelectionEdition selection_edition{context};\n            context.selections() = func(context, count, flags);\n        };\n\n        static constexpr struct ObjectType\n        {\n            Key key;\n            Optional<Selection> (*func)(const Context&, const Selection&, int, ObjectFlags);\n            Vector<Selection> (*nested_func)(const Context&, int, ObjectFlags);\n        } selectors[] = {\n            { 'w', select_word<Word>, select_nested_words<Word> },\n            { alt('w'), select_word<WORD>, select_nested_words<WORD> },\n            { 's', select_sentence, select_nested_sentences },\n            { 'p', select_paragraph, select_nested_paragraphs },\n            { Key::Space, select_whitespaces, select_nested_whitespaces },\n            { 'i', select_indent, select_nested_indents },\n            { 'n', select_number, select_nested_numbers },\n            { 'u', select_argument, select_nested_arguments },\n        };\n        auto obj_it = find(selectors | transform(&ObjectType::key), key).base();\n        if (obj_it != std::end(selectors))\n        {\n            if (nested)\n                return select_nested_and_set_last(context, obj_it->nested_func);\n            else\n                return select_and_set_last(\n                    context, mode, [=](Context& context, Selection& sel) { return obj_it->func(context, sel, count, flags); });\n        }\n\n        static constexpr auto regex_selector = [=](StringView open, StringView close, int count, ObjectFlags flags) {\n            return [open=Regex{open, RegexCompileFlags::Backward},\n                    close=Regex{close, RegexCompileFlags::Backward},\n                    count, flags](Context& context, Selection& sel) {\n                return select_surrounding(context, sel, open, close, count, flags);\n            };\n        };\n\n        if (key == 'c')\n        {\n            const bool info = show_auto_info_ifn(\n                \"Enter object desc\",\n                \"format: <open regex>,<close regex>\\n\"\n                \"        escape commas with '\\\\'\",\n                AutoInfo::Command, context);\n\n            context.input_handler().prompt(\n                \"object desc:\", {}, {}, context.faces()[\"Prompt\"],\n                PromptFlags::None, '_', complete_nothing,\n                [count,info,mode,nested,flags,select_nested_and_set_last](StringView cmdline, PromptEvent event, Context& context) {\n                    if (event != PromptEvent::Change)\n                        hide_auto_info_ifn(context, info);\n                    if (event != PromptEvent::Validate)\n                        return;\n\n                    struct error : runtime_error { error(size_t) : runtime_error{\"desc parsing failed, expected <open>,<close>\"} {} };\n\n                    auto params = cmdline | split<StringView>(',', '\\\\')\n                                          | transform(unescape<',', '\\\\'>)\n                                          | static_gather<error, 2>();\n                    if (params[0].empty() or params[1].empty())\n                        throw error{0};\n\n                    if (nested)\n                        return select_nested_and_set_last(context, [open=Regex{params[0]},close=Regex{params[1]}](Context& context, int count, ObjectFlags flags) {\n                            return open == close ? regex_select_nested(context, open, flags)\n                                                 : regex_select_nested(context, open, close, count, flags);\n                        });\n                    else\n                        select_and_set_last(context, mode, regex_selector(params[0], params[1], count, flags));\n                });\n            return;\n        }\n\n        if (key == alt(';'))\n        {\n            EnvVarMap env_vars = {\n                { \"count\", to_string(params.count) },\n                { \"register\", String{&params.reg, 1} },\n                { \"select_mode\", option_to_string(mode, Quoting::Raw) },\n                { \"object_flags\", option_to_string(flags, Quoting::Raw) }\n            };\n            command(context, std::move(env_vars));\n            return;\n        }\n\n        static constexpr struct\n        {\n            char open;\n            char close;\n            char name;\n        } surrounding_pairs[] = {\n            { '(', ')', 'b' },\n            { '{', '}', 'B' },\n            { '[', ']', 'r' },\n            { '<', '>', 'a' },\n            { '\"', '\"', 'Q' },\n            { '\\'', '\\'', 'q' },\n            { '`', '`', 'g' },\n        };\n        if (auto it = find_if(surrounding_pairs, [key](auto s) { return key == s.open or key == s.close or key == s.name; });\n            it != std::end(surrounding_pairs))\n        {\n            if (nested)\n                return select_nested_and_set_last(context, [open=Regex{format(\"\\\\Q{}\", it->open)},close=Regex{format(\"\\\\Q{}\", it->close)}](Context& context, int count, ObjectFlags flags) {\n                    return open == close ? regex_select_nested(context, open, flags)\n                                         : regex_select_nested(context, open, close, count, flags);\n                });\n            else\n                return select_and_set_last(\n                    context, mode, regex_selector(format(\"\\\\Q{}\", it->open), format(\"\\\\Q{}\", it->close), count, flags));\n        }\n\n        if (auto cp = key.codepoint(); cp and is_punctuation(*cp, {}))\n        {\n            auto re = \"\\\\Q\" + to_string(*cp);\n            if (nested)\n                return select_nested_and_set_last(context, [re](Context& context, int count, ObjectFlags flags) {\n                    return regex_select_nested(context, Regex{re}, flags);\n                });\n            else\n                return select_and_set_last(context, mode, regex_selector(re, re, count, flags));\n        }\n    }, get_title(),\n    build_autoinfo_for_mapping(context, KeymapMode::Object,\n        {{{'b','(',')'}, \"parenthesis block\"},\n         {{'B','{','}'}, \"brace block\"},\n         {{'r','[',']'}, \"bracket block\"},\n         {{'a','<','>'}, \"angle block\"},\n         {{'\"','Q'},     \"double quote string\"},\n         {{'\\'','q'},    \"single quote string\"},\n         {{'`','g'},     \"grave quote string\"},\n         {{'w'},         \"word\"},\n         {{alt('w')},    \"WORD\"},\n         {{'s'},         \"sentence\"},\n         {{'p'},         \"paragraph\"},\n         {{Key::Space},  \"whitespaces\"},\n         {{'i'},         \"indent\"},\n         {{'u'},         \"argument\"},\n         {{'n'},         \"number\"},\n         {{'c'},         \"custom object desc\"},\n         {{alt(';')},    \"run command in object context\"}}));\n}\n\ntemplate<ObjectFlags flags, SelectMode mode = SelectMode::Replace>\nvoid select_object(Context& context, NormalParams params)\n{\n    static_assert((not (flags & ObjectFlags::Nested)) or (flags & (ObjectFlags::ToBegin | ObjectFlags::ToEnd)),\n                  \"nested selections only can select whole nested objects\");\n    return select_object(context, params, flags, mode);\n}\n\ntemplate<Direction direction, bool half = false>\nvoid scroll(Context& context, NormalParams params)\n{\n    const Window& window = context.window();\n    const int count = params.count ? params.count : 1;\n    const LineCount offset = (window.dimensions().line - 2) / (half ? 2 : 1) * count;\n\n    scroll_window(context, offset * direction, OnHiddenCursor::MoveCursorAndAnchor);\n}\n\ntemplate<Direction direction>\nvoid copy_selections_on_next_lines(Context& context, NormalParams params)\n{\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    auto& buffer = context.buffer();\n    const ColumnCount tabstop = context.options()[\"tabstop\"].get<int>();\n    Vector<Selection> result;\n    size_t main_index = 0;\n    for (auto& sel : selections)\n    {\n        const bool is_main = (&sel == &selections.main());\n        auto anchor = sel.anchor();\n        auto cursor = sel.cursor();\n        ColumnCount cursor_col = get_column(buffer, tabstop, cursor);\n        ColumnCount anchor_col = get_column(buffer, tabstop, anchor);\n\n        if (is_main)\n            main_index = result.size();\n        result.push_back(std::move(sel));\n        const LineCount height = std::max(anchor.line, cursor.line) - std::min(anchor.line, cursor.line) + 1;\n        const size_t max_lines = std::max(params.count, 1);\n\n        for (size_t i = 0, nb_sels = 0; nb_sels < max_lines; ++i)\n        {\n            LineCount offset = direction * (i + 1) * height;\n\n            const LineCount anchor_line = anchor.line + offset;\n            const LineCount cursor_line = cursor.line + offset;\n\n            if (anchor_line < 0 or cursor_line < 0 or\n                anchor_line >= buffer.line_count() or cursor_line >= buffer.line_count())\n                break;\n\n            const ByteCount anchor_byte = get_byte_to_column(buffer, tabstop, {anchor_line, anchor_col});\n            const ByteCount cursor_byte = get_byte_to_column(buffer, tabstop, {cursor_line, cursor_col});\n\n            if (anchor_byte != buffer[anchor_line].length() and\n                cursor_byte != buffer[cursor_line].length())\n            {\n                if (is_main)\n                    main_index = result.size();\n                result.emplace_back(BufferCoord{anchor_line, anchor_byte},\n                                    BufferCoordAndTarget{cursor_line, cursor_byte, cursor.target});\n\n                nb_sels++;\n            }\n        }\n    }\n    selections.set(std::move(result), main_index);\n    selections.sort_and_merge_overlapping();\n}\n\ntemplate<Direction direction>\nvoid rotate_selections(Context& context, NormalParams params)\n{\n    const int count = params.count ? params.count : 1;\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    const int index = selections.main_index();\n    const int num = selections.size();\n    selections.set_main_index((direction == Forward) ?\n                                (index + count) % num\n                              : (index + (num - count % num)) % num);\n}\n\ntemplate<Direction direction>\nvoid rotate_selections_content(Context& context, NormalParams params)\n{\n    size_t group = params.count;\n    size_t count = 1;\n    auto strings = context.selections_content();\n    if (group == 0 or group > (int)strings.size())\n        group = (int)strings.size();\n    count = count % group;\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    auto main = strings.begin() + selections.main_index();\n    for (auto it = strings.begin(); it != strings.end(); )\n    {\n        auto end = std::min(strings.end(), it + group);\n        auto new_beg = (direction == Direction::Forward) ? end - count : it + count;\n        std::rotate(it, new_beg, end);\n\n        if (it <= main and main < end)\n            main = main < new_beg ? end - (new_beg - main) : it + (main - new_beg);\n        it = end;\n    }\n    selections.replace(strings);\n    selections.set_main_index(main - strings.begin());\n}\n\nenum class SelectFlags\n{\n    None = 0,\n    Reverse = 1,\n    Inclusive = 2,\n    Extend = 4\n};\n\nconstexpr bool with_bit_ops(Meta::Type<SelectFlags>) { return true; }\n\nvoid select_to_next_char(Context& context, NormalParams params, SelectFlags flags)\n{\n    auto get_title = [=] {\n        return format(\"{} {} {} char\",\n                      flags & SelectFlags::Extend ? \"extend\" : \"select\",\n                      flags & SelectFlags::Inclusive ? \"onto\" : \"to\",\n                      flags & SelectFlags::Reverse ? \"previous\" : \"next\");\n    };\n\n    on_next_key_with_autoinfo(context, \"to-char\", KeymapMode::None,\n                             [params, flags](Key key, Context& context) {\n        auto cp = key.codepoint();\n        if (not cp or key == Key::Escape)\n            return;\n        auto new_flags = flags & SelectFlags::Extend ? SelectMode::Extend\n                                                               : SelectMode::Replace;\n        select_and_set_last(\n            context, new_flags, [cp=*cp, count=params.count, flags] (auto& context, auto& sel) {\n                auto& func = flags & SelectFlags::Reverse ? select_to_reverse : select_to;\n                return func(context, sel, cp, count, flags & SelectFlags::Inclusive);\n            });\n    }, get_title(), \"enter char to select to\");\n}\n\ntemplate<SelectFlags flags>\nvoid select_to_next_char(Context& context, NormalParams params)\n{\n    select_to_next_char(context, params, flags);\n}\n\nvoid start_or_end_macro_recording(Context& context, NormalParams params)\n{\n    if (context.input_handler().is_recording())\n        context.input_handler().stop_recording();\n    else\n    {\n        const char reg = to_lower(params.reg ? params.reg : '@');\n        if (not is_basic_alpha(reg) and reg != '@')\n            throw runtime_error(\"macros can only use the '@' and alphabetic registers\");\n        context.input_handler().start_recording(reg);\n    }\n}\n\nvoid replay_macro(Context& context, NormalParams params)\n{\n    const char reg = to_lower(params.reg ? params.reg : '@');\n    if (not is_basic_alpha(reg) and reg != '@')\n        throw runtime_error(\"macros can only use the '@' and alphabetic registers\");\n\n    static bool running_macros[27] = {};\n    const size_t idx = reg != '@' ? (size_t)(reg - 'a') : 26;\n    if (running_macros[idx])\n        throw runtime_error(\"recursive macros call detected\");\n\n    ConstArrayView<String> reg_val = RegisterManager::instance()[reg].get(context);\n    if (reg_val.empty() or reg_val[0].empty())\n        throw runtime_error(format(\"register '{}' is empty\", reg));\n\n    running_macros[idx] = true;\n    auto stop = OnScopeEnd([&]{ running_macros[idx] = false; });\n\n    auto keys = parse_keys(reg_val[0]);\n    ScopedEdition edition(context);\n    ScopedSelectionEdition selection_edition{context};\n    ScopedSetBool disable_keymaps(context.keymaps_disabled());\n    do\n    {\n        for (auto& key : keys)\n            context.input_handler().handle_key(key);\n    } while (--params.count > 0);\n}\n\ntemplate<Direction direction>\nvoid jump(Context& context, NormalParams params)\n{\n    const int count = std::max(1, params.count);\n    auto jump = (direction == Forward) ?\n                 context.jump_list().forward(context, count) :\n                 context.jump_list().backward(context, count);\n\n    Buffer* oldbuf = &context.buffer();\n    Buffer& buffer = const_cast<Buffer&>(jump.buffer());\n    ScopedSelectionEdition selection_edition{context};\n    if (&buffer != oldbuf)\n        context.change_buffer(buffer);\n    context.selections_write_only() = jump;\n}\n\nvoid push_selections(Context& context, NormalParams)\n{\n    context.push_jump(true);\n    context.print_status({ format(\"saved {} selections\", context.selections().size()),\n                           context.faces()[\"Information\"] });\n}\n\nvoid align(Context& context, NormalParams)\n{\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    auto& buffer = context.buffer();\n    const ColumnCount tabstop = context.options()[\"tabstop\"].get<int>();\n\n    Vector<Vector<const Selection*>> columns;\n    LineCount last_line = -1;\n    size_t column = 0;\n    for (auto& sel : selections)\n    {\n        auto line = sel.cursor().line;\n        if (sel.anchor().line != line)\n            throw runtime_error(\"align cannot work with multi line selections\");\n\n        column = (line == last_line) ? column + 1 : 0;\n        if (column >= columns.size())\n            columns.resize(column+1);\n        columns[column].push_back(&sel);\n        last_line = line;\n    }\n\n    const bool use_tabs = context.options()[\"aligntab\"].get<bool>();\n    for (auto& col : columns)\n    {\n        ColumnCount maxcol = 0;\n        for (auto& sel : col)\n            maxcol = std::max(get_column(buffer, tabstop, sel->cursor()), maxcol);\n        for (auto& sel : col)\n        {\n            auto insert_coord = sel->min();\n            ColumnCount lastcol = get_column(buffer, tabstop, sel->cursor());\n            ColumnCount inscount = maxcol - lastcol;\n            String padstr;\n            if (not use_tabs)\n                padstr = String{ ' ', inscount };\n            else\n            {\n                ColumnCount inscol = get_column(buffer, tabstop, insert_coord);\n                ColumnCount targetcol = inscol + inscount;\n                ColumnCount tabcol = inscol - (inscol % tabstop);\n                CharCount tabs = (int)((targetcol - tabcol) / tabstop);\n                CharCount spaces = (int)(targetcol - (tabs ? (tabcol + (int)tabs * tabstop) : inscol));\n                padstr = String{ '\\t', tabs } + String{ ' ', spaces };\n            }\n            buffer.insert(insert_coord, padstr);\n        }\n        selections.update();\n    }\n}\n\nvoid copy_indent(Context& context, NormalParams params)\n{\n    int selection = params.count;\n    auto& buffer = context.buffer();\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    Vector<LineCount> lines;\n    for (auto sel : selections)\n    {\n        for (LineCount l = sel.min().line; l < sel.max().line + 1; ++l)\n            lines.push_back(l);\n    }\n    if (selection > selections.size())\n        throw runtime_error(\"invalid selection index\");\n    if (selection == 0)\n        selection = context.selections().main_index() + 1;\n\n    auto ref_line = selections[selection-1].min().line;\n    auto line = buffer[ref_line];\n    auto it = line.begin();\n    while (it != line.end() and is_horizontal_blank(*it))\n        ++it;\n    const StringView indent = line.substr(0_byte, (int)(it-line.begin()));\n\n    ScopedEdition edition{context};\n    for (auto& l : lines)\n    {\n        if (l == ref_line)\n            continue;\n\n        auto line = buffer[l];\n        ByteCount i = 0;\n        while (i < line.length() and is_horizontal_blank(line[i]))\n            ++i;\n        buffer.replace(l, {l, i}, indent);\n    }\n}\n\nvoid tabs_to_spaces(Context& context, NormalParams params)\n{\n    auto& buffer = context.buffer();\n    const ColumnCount opt_tabstop = context.options()[\"tabstop\"].get<int>();\n    const ColumnCount tabstop = params.count == 0 ? opt_tabstop : params.count;\n    Vector<Selection> tabs;\n    Vector<String> spaces;\n    ScopedSelectionEdition selection_edition{context};\n    ScopedEdition edition{context};\n    for (auto& sel : context.selections())\n    {\n        for (auto it = buffer.iterator_at(sel.min()),\n                  end = buffer.iterator_at(sel.max())+1; it != end; ++it)\n        {\n            if (*it == '\\t')\n            {\n                ColumnCount col = get_column(buffer, opt_tabstop, it.coord());\n                ColumnCount end_col = (col / tabstop + 1) * tabstop;\n                tabs.emplace_back(it.coord());\n                spaces.emplace_back(' ', end_col - col);\n            }\n        }\n    }\n    if (not tabs.empty())\n        SelectionList{ buffer, std::move(tabs) }.replace(spaces);\n}\n\nvoid spaces_to_tabs(Context& context, NormalParams params)\n{\n    auto& buffer = context.buffer();\n    const ColumnCount opt_tabstop = context.options()[\"tabstop\"].get<int>();\n    const ColumnCount tabstop = params.count == 0 ? opt_tabstop : params.count;\n    Vector<Selection> spaces;\n    ScopedSelectionEdition selection_edition{context};\n    ScopedEdition edition{context};\n    for (auto& sel : context.selections())\n    {\n        for (auto it = buffer.iterator_at(sel.min()),\n                  end = buffer.iterator_at(sel.max())+1; it != end;)\n        {\n            if (*it == ' ')\n            {\n                auto spaces_beg = it;\n                auto spaces_end = spaces_beg+1;\n                ColumnCount col = get_column(buffer, opt_tabstop, spaces_end.coord());\n                while (spaces_end != end and\n                       *spaces_end == ' ' and (col % tabstop) != 0)\n                {\n                    ++spaces_end;\n                    ++col;\n                }\n                if ((col % tabstop) == 0)\n                    spaces.emplace_back(spaces_beg.coord(), (spaces_end-1).coord());\n                else if (spaces_end != end and *spaces_end == '\\t')\n                    spaces.emplace_back(spaces_beg.coord(), spaces_end.coord());\n                it = spaces_end;\n            }\n            else\n                ++it;\n        }\n    }\n    if (not spaces.empty())\n        SelectionList{ buffer, std::move(spaces) }.replace(\"\\t\"_str);\n}\n\nvoid trim_selections(Context& context, NormalParams)\n{\n    using Utf8It = utf8::iterator<BufferIterator>;\n    auto& buffer = context.buffer();\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    Vector<int> to_remove;\n\n    for (int i = 0; i < (int)selections.size(); ++i)\n    {\n        auto& sel = selections[i];\n        auto beg = Utf8It{buffer.iterator_at(sel.min()), buffer};\n        auto end = Utf8It{buffer.iterator_at(sel.max()), buffer};\n        while (beg != end and is_blank(*beg))\n            ++beg;\n        while (beg != end and is_blank(*end))\n            --end;\n\n        if (beg == end and is_blank(*beg))\n            to_remove.push_back(i);\n        else\n        {\n            sel.min() = beg.base().coord();\n            sel.max() = end.base().coord();\n        }\n    }\n\n    if (to_remove.size() == selections.size())\n        throw no_selections_remaining{};\n    for (auto& i : to_remove | reverse())\n        selections.remove(i);\n}\n\nSelectionList read_selections_from_register(char reg, const Context& context)\n{\n    if (not is_basic_alpha(reg) and reg != '^')\n        throw runtime_error(\"selections can only be saved to the '^' and alphabetic registers\");\n\n    auto content = RegisterManager::instance()[reg].get(context);\n\n    if (content.size() < 2)\n        throw runtime_error(format(\"register '{}' does not contain a selections desc\", reg));\n\n    // Use the last two values for timestamp and main_index to allow the buffer\n    // name to have @ symbols\n    struct error : runtime_error { error(size_t) : runtime_error{\"expected <buffer>@<timestamp>@main_index\"} {} };\n    auto end_content = content[0] | reverse() | split('@') | transform([] (auto bounds) {\n        return StringView{bounds.second.base(), bounds.first.base()};\n    }) | static_gather<error, 2, false>();\n\n    const size_t main = str_to_int(end_content[0]);\n    const size_t timestamp = str_to_int(end_content[1]);\n    const auto buffer_name = StringView{ content[0].begin (), end_content[1].begin () - 1 };\n    Buffer& buffer = BufferManager::instance().get_buffer(buffer_name);\n\n    return selection_list_from_strings(buffer, ColumnType::Byte, content.subrange(1), timestamp, main);\n}\n\nenum class CombineOp\n{\n    Append,\n    Union,\n    Intersect,\n    SelectLeftmostCursor,\n    SelectRightmostCursor,\n    SelectLongest,\n    SelectShortest,\n};\n\nCombineOp key_to_combine_op(Key key)\n{\n    switch (key.key)\n    {\n        case 'a': return CombineOp::Append;\n        case 'u': return CombineOp::Union;\n        case 'i': return CombineOp::Intersect;\n        case '<': return CombineOp::SelectLeftmostCursor;\n        case '>': return CombineOp::SelectRightmostCursor;\n        case '+': return CombineOp::SelectLongest;\n        case '-': return CombineOp::SelectShortest;\n    }\n    throw runtime_error{format(\"no such combine operator: '{}'\", key.key)};\n}\n\nvoid combine_selection(const Buffer& buffer, Selection& sel, const Selection& other, CombineOp op)\n{\n    switch (op)\n    {\n        case CombineOp::Union:\n            sel.set(std::min(sel.min(), other.min()),\n                    std::max(sel.max(), other.max()));\n            break;\n        case CombineOp::Intersect:\n            sel.set(std::max(sel.min(), other.min()),\n                    std::min(sel.max(), other.max()));\n            break;\n        case CombineOp::SelectLeftmostCursor:\n            if (sel.cursor() > other.cursor())\n                sel = other;\n            break;\n        case CombineOp::SelectRightmostCursor:\n            if (sel.cursor() < other.cursor())\n                sel = other;\n            break;\n        case CombineOp::SelectLongest:\n            if (char_length(buffer, sel) < char_length(buffer, other))\n                sel = other;\n            break;\n        case CombineOp::SelectShortest:\n            if (char_length(buffer, sel) > char_length(buffer, other))\n                sel = other;\n            break;\n        default: kak_assert(false);\n    }\n}\n\ntemplate<typename Func>\nvoid combine_selections(Context& context, SelectionList list, Func func, StringView title)\n{\n    if (&context.buffer() != &list.buffer())\n        throw runtime_error{\"cannot combine selections from different buffers\"};\n\n    on_next_key_with_autoinfo(context, \"combine-selections\", KeymapMode::None,\n                             [func, list](Key key, Context& context) mutable {\n                                 if (key == Key::Escape)\n                                     return;\n\n                                 const auto op = key_to_combine_op(key);\n                                 ScopedSelectionEdition selection_edition{context};\n                                 auto& sels = context.selections();\n                                 list.update();\n                                 if (op == CombineOp::Append)\n                                 {\n                                     const auto main_index = list.size() + sels.main_index();\n                                     for (auto& sel : sels)\n                                         list.push_back(sel);\n                                     list.set_main_index(main_index);\n                                     list.sort_and_merge_overlapping();\n                                 }\n                                 else\n                                 {\n                                     if (list.size() != sels.size())\n                                         throw runtime_error{format(\"the two selection lists don't have the same number of elements ({} vs {})\",\n                                                                    list.size(), sels.size())};\n                                     for (int i = 0; i < list.size(); ++i)\n                                         combine_selection(sels.buffer(), list[i], sels[i], op);\n                                     list.set_main_index(sels.main_index());\n                                 }\n                                 func(context, std::move(list));\n                             }, title.str(),\n                             \"'a': append lists\\n\"\n                             \"'u': union\\n\"\n                             \"'i': intersection\\n\"\n                             \"'<': select leftmost cursor\\n\"\n                             \"'>': select rightmost cursor\\n\"\n                             \"'+': select longest\\n\"\n                             \"'-': select shortest\\n\");\n}\n\ntemplate<bool combine>\nvoid save_selections(Context& context, NormalParams params)\n{\n    const char reg = to_lower(params.reg ? params.reg : '^');\n    if (not is_basic_alpha(reg) and reg != '^')\n        throw runtime_error(\"selections can only be saved to the '^' and alphabetic registers\");\n\n    auto content = RegisterManager::instance()[reg].get(context);\n    const bool empty = content.size() == 1 and content[0].empty();\n\n    auto save_to_reg = [reg](Context& context, const SelectionList& sels) {\n        auto& buffer = context.buffer();\n        auto to_string = [&] (const Selection& sel) { return selection_to_string(ColumnType::Byte, buffer, sel); };\n        auto descs = concatenated(ConstArrayView<String>{format(\"{}@{}@{}\", buffer.name(), buffer.timestamp(), sels.main_index())},\n                                  sels | transform(to_string)) | gather<Vector<String>>();\n        RegisterManager::instance()[reg].set(context, descs);\n\n        context.print_status({format(\"{} {} selections to register '{}'\",\n                                     combine ? \"Combined\" : \"Saved\", sels.size(), reg),\n                              context.faces()[\"Information\"]});\n    };\n\n    if (combine and not empty)\n    {\n        ScopedSelectionEdition selection_edition{context};\n        combine_selections(context, read_selections_from_register(reg, context), save_to_reg, \"combine selections to register\");\n    }\n    else\n        save_to_reg(context, context.selections());\n}\n\ntemplate<bool combine>\nvoid restore_selections(Context& context, NormalParams params)\n{\n    const char reg = to_lower(params.reg ? params.reg : '^');\n    auto selections = read_selections_from_register(reg, context);\n\n    ScopedSelectionEdition selection_edition{context};\n\n    auto set_selections = [reg](Context& context, SelectionList sels) {\n        auto size = sels.size();\n        context.selections_write_only() = std::move(sels);\n        context.print_status({format(\"{} {} selections from register '{}'\",\n                                     combine ? \"Combined\" : \"Restored\", size, reg),\n                              context.faces()[\"Information\"]});\n    };\n\n    if (not combine)\n    {\n        if (&selections.buffer() != &context.buffer())\n            context.change_buffer(selections.buffer());\n        set_selections(context, std::move(selections));\n    }\n    else\n        combine_selections(context, std::move(selections), set_selections, \"combine selections from register\");\n}\n\nvoid undo(Context& context, NormalParams params)\n{\n    Buffer& buffer = context.buffer();\n    size_t timestamp = buffer.timestamp();\n    if (buffer.undo(std::max(1, params.count)))\n    {\n        auto ranges = compute_modified_ranges(buffer, timestamp);\n        if (not ranges.empty())\n            context.selections_write_only() = std::move(ranges);\n    }\n    else\n        throw runtime_error(\"nothing left to undo\");\n}\n\nvoid redo(Context& context, NormalParams params)\n{\n    Buffer& buffer = context.buffer();\n    size_t timestamp = buffer.timestamp();\n    if (buffer.redo(std::max(1, params.count)))\n    {\n        auto ranges = compute_modified_ranges(buffer, timestamp);\n        if (not ranges.empty())\n            context.selections_write_only() = std::move(ranges);\n    }\n    else\n        throw runtime_error(\"nothing left to redo\");\n}\n\ntemplate<Direction direction>\nvoid move_in_history(Context& context, NormalParams params)\n{\n    Buffer& buffer = context.buffer();\n    size_t timestamp = buffer.timestamp();\n    const int count = std::max(1, params.count);\n    const int history_id = (size_t)buffer.current_history_id() + direction * count;\n    const int max_history_id = (int)buffer.next_history_id() - 1;\n    if (buffer.move_to((Buffer::HistoryId)history_id))\n    {\n        auto ranges = compute_modified_ranges(buffer, timestamp);\n        if (not ranges.empty())\n            context.selections_write_only() = std::move(ranges);\n\n        context.print_status({ format(\"moved to change #{} ({})\",\n                               history_id, max_history_id),\n                               context.faces()[\"Information\"] });\n    }\n    else\n        throw runtime_error(format(\"no such change: #{} ({})\",\n                            history_id, max_history_id));\n}\n\ntemplate<Direction direction>\nvoid undo_selection_change(Context& context, NormalParams params)\n{\n    int count = std::max(1, params.count);\n    while (count--)\n        context.undo_selection_change<direction>();\n}\n\nvoid exec_user_mappings(Context& context, NormalParams params)\n{\n    on_next_key_with_autoinfo(context, \"user-mapping\", KeymapMode::None,\n                             [params](Key key, Context& context) mutable {\n        if (not context.keymaps().is_mapped(key, KeymapMode::User))\n            return;\n\n        ScopedSetBool disable_keymaps(context.keymaps_disabled());\n\n        InputHandler::ScopedForceNormal force_normal{context.input_handler(), params};\n\n        ScopedEdition edition(context);\n        ScopedSelectionEdition selection_edition{context};\n        for (auto& key : context.keymaps().get_mapping_keys(key, KeymapMode::User))\n            context.input_handler().handle_key(key);\n    }, \"user mapping\",\n    build_autoinfo_for_mapping(context, KeymapMode::User, {}));\n}\n\ntemplate<bool above>\nvoid add_empty_line(Context& context, NormalParams params)\n{\n    int count = std::max(params.count, 1);\n    String new_lines{'\\n', CharCount{count}};\n    auto& buffer = context.buffer();\n    ScopedSelectionEdition selection_edition{context};\n    auto& sels = context.selections();\n    ScopedEdition edition{context};\n    for (int i = 0; i < sels.size(); ++i)\n    {\n        auto line = (above ? sels[i].min().line : sels[i].max().line + 1) + (i * count);\n        buffer.insert(line, new_lines);\n    }\n}\n\ntemplate<typename T>\nclass Repeated\n{\npublic:\n    constexpr Repeated(T t) : m_func(t) {}\n\n    void operator() (Context& context, NormalParams params)\n    {\n        ScopedEdition edition(context);\n        ScopedSelectionEdition selection_edition{context};\n        do { m_func(context, {0, params.reg}); } while(--params.count > 0);\n    }\nprivate:\n    T m_func;\n};\n\ntemplate<void (*func)(Context&, NormalParams)>\nvoid repeated(Context& context, NormalParams params)\n{\n    ScopedEdition edition(context);\n    ScopedSelectionEdition selection_edition{context};\n    do { func(context, {0, params.reg}); } while(--params.count > 0);\n}\n\ntemplate<typename Type>\nvoid move_cursor(Context& context, NormalParams params, Direction direction, SelectMode mode)\n{\n    kak_assert(mode == SelectMode::Replace or mode == SelectMode::Extend);\n    const Type offset{direction * std::max(params.count,1)};\n    const ColumnCount tabstop = context.options()[\"tabstop\"].get<int>();\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    for (auto& sel : selections)\n    {\n        auto cursor = context.buffer().offset_coord(sel.cursor(), offset, tabstop);\n        sel.anchor() = mode == SelectMode::Extend ? sel.anchor() : cursor;\n        sel.cursor() = cursor;\n    }\n    selections.sort_and_merge_overlapping();\n}\n\ntemplate<typename Type, Direction direction, SelectMode mode = SelectMode::Replace>\nvoid move_cursor(Context& context, NormalParams params)\n{\n    move_cursor<Type>(context, params, direction, mode);\n}\n\nvoid select_whole_buffer(Context& context, NormalParams)\n{\n    auto& buffer = context.buffer();\n    ScopedSelectionEdition selection_edition{context};\n    context.selections_write_only() = SelectionList{buffer, {{0,0}, {buffer.back_coord(), max_column}}};\n}\n\nvoid keep_selection(Context& context, NormalParams p)\n{\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    const int index = p.count ? p.count-1 : selections.main_index();\n    if (index >= selections.size())\n        throw runtime_error{format(\"invalid selection index: {}\", index)};\n\n    selections = SelectionList{ selections.buffer(), std::move(selections[index]) };\n    selections.check_invariant();\n}\n\nvoid remove_selection(Context& context, NormalParams p)\n{\n    ScopedSelectionEdition selection_edition{context};\n    auto& selections = context.selections();\n    const int index = p.count ? p.count-1 : selections.main_index();\n    if (index >= selections.size())\n        throw runtime_error{format(\"invalid selection index: {}\", index)};\n    if (selections.size() == 1)\n        throw no_selections_remaining{};\n\n    selections.remove(index);\n    selections.check_invariant();\n}\n\nvoid clear_selections(Context& context, NormalParams)\n{\n    ScopedSelectionEdition selection_edition{context};\n    for (auto& sel : context.selections())\n        sel.anchor() = sel.cursor();\n}\n\nvoid flip_selections(Context& context, NormalParams)\n{\n    ScopedSelectionEdition selection_edition{context};\n    for (auto& sel : context.selections())\n    {\n        const BufferCoord tmp = sel.anchor();\n        sel.anchor() = sel.cursor();\n        sel.cursor() = tmp;\n    }\n    context.selections().check_invariant();\n}\n\nvoid ensure_forward(Context& context, NormalParams)\n{\n    ScopedSelectionEdition selection_edition{context};\n    for (auto& sel : context.selections())\n    {\n        const BufferCoord min = sel.min(), max = sel.max();\n        sel.anchor() = min;\n        sel.cursor() = max;\n    }\n    context.selections().check_invariant();\n}\n\nvoid merge_consecutive(Context& context, NormalParams params)\n{\n    ScopedSelectionEdition selection_edition{context};\n    ensure_forward(context, params);\n    context.selections().merge_consecutive();\n}\n\nvoid merge_overlapping(Context& context, NormalParams params)\n{\n    ScopedSelectionEdition selection_edition{context};\n    ensure_forward(context, params);\n    context.selections().merge_overlapping();\n}\n\nvoid duplicate_selections(Context& context, NormalParams params)\n{\n    ScopedSelectionEdition selection_edition{context};\n    SelectionList& sels = context.selections();\n    Vector<Selection> new_sels;\n    const int count = params.count ? params.count : 2;\n    BasicSelection last{BufferCoord{-1,-1}};\n    size_t index = 0;\n    size_t main_index = 0;\n    for (const auto& sel : sels)\n    {\n        new_sels.insert(new_sels.end(), sel != last ? count : 1, sel);\n        last = sel;\n        if (index++ == sels.main_index())\n            main_index = new_sels.size() - 1;\n    }\n    context.selections().set(std::move(new_sels), main_index);\n}\n\nvoid force_redraw(Context& context, NormalParams)\n{\n    if (context.has_client())\n    {\n        context.client().force_redraw(true);\n        context.client().redraw_ifn();\n    }\n}\n\nconstexpr size_t keymap_max_size = 512;\n\ntemplate<typename T, MemoryDomain domain>\nstruct KeymapBackend : private Array<T, keymap_max_size>\n{\n    using iterator = const T*;\n    using const_iterator = const T*;\n\n    constexpr KeymapBackend() = default;\n    constexpr KeymapBackend(std::initializer_list<T> items)\n    {\n        resize(items.size());\n        for (size_t i = 0; i < items.size(); ++i)\n            this->m_data[i] = *(items.begin() + i);\n    }\n\n    constexpr void resize(size_t s)\n    {\n        if (s > keymap_max_size)\n            throw runtime_error{\"cannot resize\"};\n        m_size = s;\n    }\n    constexpr size_t size() const { return m_size; }\n\n    using KeymapBackend::Array::begin;\n    using KeymapBackend::Array::end;\n    using KeymapBackend::Array::operator[];\n\nprivate:\n    size_t m_size = 0;\n};\n\nstatic constexpr HashMap<Key, NormalCmd, MemoryDomain::Undefined, KeymapBackend> keymap = {\n    { {'h'}, {\"move left\", move_cursor<CharCount, Backward>} },\n    { {'j'}, {\"move down\", move_cursor<LineCount, Forward>} },\n    { {'k'}, {\"move up\",  move_cursor<LineCount, Backward>} },\n    { {'l'}, {\"move right\", move_cursor<CharCount, Forward>} },\n\n    { {'H'}, {\"extend left\", move_cursor<CharCount, Backward, SelectMode::Extend>} },\n    { {'J'}, {\"extend down\", move_cursor<LineCount, Forward, SelectMode::Extend>} },\n    { {'K'}, {\"extend up\", move_cursor<LineCount, Backward, SelectMode::Extend>} },\n    { {'L'}, {\"extend right\", move_cursor<CharCount, Forward, SelectMode::Extend>} },\n\n    { {'t'}, {\"select to next character\", select_to_next_char<SelectFlags::None>} },\n    { {'f'}, {\"select to next character included\", select_to_next_char<SelectFlags::Inclusive>} },\n    { {'T'}, {\"extend to next character\", select_to_next_char<SelectFlags::Extend>} },\n    { {'F'}, {\"extend to next character included\", select_to_next_char<SelectFlags::Inclusive | SelectFlags::Extend>} },\n    { {alt('t')}, {\"select to previous character\", select_to_next_char<SelectFlags::Reverse>} },\n    { {alt('f')}, {\"select to previous character included\", select_to_next_char<SelectFlags::Inclusive | SelectFlags::Reverse>} },\n    { {alt('T')}, {\"extend to previous character\", select_to_next_char<SelectFlags::Extend | SelectFlags::Reverse>} },\n    { {alt('F')}, {\"extend to previous character included\", select_to_next_char<SelectFlags::Inclusive | SelectFlags::Extend | SelectFlags::Reverse>} },\n\n    { {'d'}, {\"erase selected text\", erase_selections<true>} },\n    { {alt('d')}, {\"erase selected text, without yanking\", erase_selections<false>} },\n    { {'c'}, {\"change selected text\", change<true>} },\n    { {alt('c')}, {\"change selected text, without yanking\", change<false>} },\n    { {'i'}, {\"insert before selected text\", enter_insert_mode<InsertMode::Insert>} },\n    { {'I'}, {\"insert at line begin\", enter_insert_mode<InsertMode::InsertAtLineBegin>} },\n    { {'a'}, {\"insert after selected text\", enter_insert_mode<InsertMode::Append>} },\n    { {'A'}, {\"insert at line end\", enter_insert_mode<InsertMode::AppendAtLineEnd>} },\n    { {'o'}, {\"insert on new line below\", enter_insert_mode<InsertMode::OpenLineBelow>} },\n    { {'O'}, {\"insert on new line above\", enter_insert_mode<InsertMode::OpenLineAbove>} },\n    { {'r'}, {\"replace with character\", replace_with_char} },\n\n    { {alt('o')}, {\"add a new empty line below\", add_empty_line<false>} },\n    { {alt('O')}, {\"add a new empty line above\", add_empty_line<true>} },\n\n    { {'g'}, {\"go to location\", goto_commands<SelectMode::Replace>} },\n    { {'G'}, {\"extend to location\", goto_commands<SelectMode::Extend>} },\n\n    { {'v'}, {\"move view\", view_commands<false>} },\n    { {'V'}, {\"move view (locked)\", view_commands<true>} },\n\n    { {'y'}, {\"yank selected text\", yank} },\n    { {'p'}, {\"paste after selected text\", repeated<paste<PasteMode::Append>>} },\n    { {'P'}, {\"paste before selected text\", repeated<paste<PasteMode::Insert>>} },\n    { {alt('p')}, {\"paste every yanked selection after selected text\", paste_all<PasteMode::Append>} },\n    { {alt('P')}, {\"paste every yanked selection before selected text\", paste_all<PasteMode::Insert>} },\n    { {'R'}, {\"replace selected text with yanked text\", paste<PasteMode::Replace>} },\n    { {alt('R')}, {\"replace selected text with every yanked text\", paste_all<PasteMode::Replace>} },\n\n    { {'s'}, {\"select regex matches in selected text\", select_regex} },\n    { {'S'}, {\"split selected text on regex matches\", split_regex} },\n    { {alt('s')}, {\"split selected text on line ends\", split_lines} },\n    { {alt('S')}, {\"select selection boundaries\", select_boundaries} },\n\n    { {'.'}, {\"repeat last insert command\", repeat_last_insert} },\n    { {alt('.')}, {\"repeat last object select/character find\", repeat_last_select} },\n\n    { {'%'}, {\"select whole buffer\", select_whole_buffer} },\n\n    { {':'}, {\"enter command prompt\", command} },\n    { {'|'}, {\"pipe each selection through filter and replace with output\", pipe<true>} },\n    { {alt('|')}, {\"pipe each selection through command and ignore output\", pipe<false>} },\n    { {'!'}, {\"insert command output\", insert_output<PasteMode::Insert>} },\n    { {alt('!')}, {\"append command output\", insert_output<PasteMode::Append>} },\n\n    { {','}, {\"remove all selections except main\", keep_selection} },\n    { {alt(',')}, {\"remove main selection\", remove_selection} },\n    { {';'}, {\"reduce selections to their cursor\", clear_selections} },\n    { {alt(';')}, {\"swap selections cursor and anchor\", flip_selections} },\n    { {alt(':')}, {\"ensure selection cursor is after anchor\", ensure_forward} },\n    { {alt('_')}, {\"merge consecutive selections\", merge_consecutive} },\n    { {'+'}, {\"duplicate each selection\", duplicate_selections} },\n    { {alt('+')}, {\"merge overlapping selections\", merge_overlapping} },\n\n    { {'w'}, {\"select to next word start\", repeated<&select<SelectMode::Replace, select_to_next_word<Word>>>} },\n    { {'e'}, {\"select to next word end\", repeated<select<SelectMode::Replace, select_to_next_word_end<Word>>>} },\n    { {'b'}, {\"select to previous word start\", repeated<select<SelectMode::Replace, select_to_previous_word<Word>>>} },\n    { {'W'}, {\"extend to next word start\", repeated<select<SelectMode::Extend, select_to_next_word<Word>>>} },\n    { {'E'}, {\"extend to next word end\", repeated<select<SelectMode::Extend, select_to_next_word_end<Word>>>} },\n    { {'B'}, {\"extend to previous word start\", repeated<select<SelectMode::Extend, select_to_previous_word<Word>>>} },\n\n    { {alt('w')}, {\"select to next WORD start\", repeated<select<SelectMode::Replace, select_to_next_word<WORD>>>} },\n    { {alt('e')}, {\"select to next WORD end\", repeated<select<SelectMode::Replace, select_to_next_word_end<WORD>>>} },\n    { {alt('b')}, {\"select to previous WORD start\", repeated<select<SelectMode::Replace, select_to_previous_word<WORD>>>} },\n    { {alt('W')}, {\"extend to next WORD start\", repeated<select<SelectMode::Extend, select_to_next_word<WORD>>>} },\n    { {alt('E')}, {\"extend to next WORD end\", repeated<select<SelectMode::Extend, select_to_next_word_end<WORD>>>} },\n    { {alt('B')}, {\"extend to previous WORD start\", repeated<select<SelectMode::Extend, select_to_previous_word<WORD>>>} },\n\n    { {alt('l')}, {\"select to line end\", repeated<select<SelectMode::Replace, select_to_line_end<false>>>} },\n    { {alt('L')}, {\"extend to line end\", repeated<select<SelectMode::Extend, select_to_line_end<false>>>} },\n    { {alt('h')}, {\"select to line begin\", repeated<select<SelectMode::Replace, select_to_line_begin<false>>>} },\n    { {alt('H')}, {\"extend to line begin\", repeated<select<SelectMode::Extend, select_to_line_begin<false>>>} },\n\n    { {'x'}, {\"extend selections to whole lines\", select<SelectMode::Replace, select_lines>} },\n    { {alt('x')}, {\"crop selections to whole lines\", select<SelectMode::Replace, trim_partial_lines>} },\n\n    { {'m'}, {\"select to matching character\", select<SelectMode::Replace, select_matching<true>>} },\n    { {alt('m')}, {\"backward select to matching character\", select<SelectMode::Replace, select_matching<false>>} },\n    { {'M'}, {\"extend to matching character\", select<SelectMode::Extend, select_matching<true>>} },\n    { {alt('M')}, {\"backward extend to matching character\", select<SelectMode::Extend, select_matching<false>>} },\n\n    { {'/'}, {\"select next given regex match\", [](Context& c, NormalParams p) { return search(c, p, SelectMode::Replace, RegexMode::Forward); } } },\n    { {'?'}, {\"extend with next given regex match\", [](Context& c, NormalParams p) { return search(c, p, SelectMode::Extend, RegexMode::Forward); } } },\n    { {alt('/')}, {\"select previous given regex match\", [](Context& c, NormalParams p) { return search(c, p, SelectMode::Replace, RegexMode::Backward); } } },\n    { {alt('?')}, {\"extend with previous given regex match\", [](Context& c, NormalParams p) { return search(c, p, SelectMode::Extend, RegexMode::Backward); } } },\n    { {'n'}, {\"select next current search pattern match\", [](Context& c, NormalParams p) { return search_next(c, p, SelectMode::Replace, RegexMode::Forward); } } },\n    { {'N'}, {\"extend with next current search pattern match\", [](Context& c, NormalParams p) { return search_next(c, p, SelectMode::Append, RegexMode::Forward); } } },\n    { {alt('n')}, {\"select previous current search pattern match\", [](Context& c, NormalParams p) { return search_next(c, p, SelectMode::Replace, RegexMode::Backward); } } },\n    { {alt('N')}, {\"extend with previous current search pattern match\", [](Context& c, NormalParams p) { return search_next(c, p, SelectMode::Append, RegexMode::Backward); } } },\n    { {'*'}, {\"set search pattern to main selection content\", use_selection_as_search_pattern<true>} },\n    { {alt('*')}, {\"set search pattern to main selection content, do not detect words\", use_selection_as_search_pattern<false>} },\n\n    { {'u'}, {\"undo\", undo} },\n    { {'U'}, {\"redo\", redo} },\n    { {ctrl('k')}, {\"move backward in history\", move_in_history<Direction::Backward>} },\n    { {ctrl('j')}, {\"move forward in history\", move_in_history<Direction::Forward>} },\n\n    { {alt('u')}, {\"undo selection change\", undo_selection_change<Backward>} },\n    { {alt('U')}, {\"redo selection change\", undo_selection_change<Forward>} },\n\n    { {alt('i')}, {\"select inner object\", select_object<ObjectFlags::ToBegin | ObjectFlags::ToEnd | ObjectFlags::Inner>} },\n    { {alt('a')}, {\"select whole object\", select_object<ObjectFlags::ToBegin | ObjectFlags::ToEnd>} },\n    { {'['}, {\"select to object start\", select_object<ObjectFlags::ToBegin>} },\n    { {']'}, {\"select to object end\", select_object<ObjectFlags::ToEnd>} },\n    { {'{'}, {\"extend to object start\", select_object<ObjectFlags::ToBegin, SelectMode::Extend>} },\n    { {'}'}, {\"extend to object end\", select_object<ObjectFlags::ToEnd, SelectMode::Extend>} },\n    { {alt('[')}, {\"select to inner object start\", select_object<ObjectFlags::ToBegin | ObjectFlags::Inner>} },\n    { {alt(']')}, {\"select to inner object end\", select_object<ObjectFlags::ToEnd | ObjectFlags::Inner>} },\n    { {alt('{')}, {\"extend to inner object start\", select_object<ObjectFlags::ToBegin | ObjectFlags::Inner, SelectMode::Extend>} },\n    { {alt('}')}, {\"extend to inner object end\", select_object<ObjectFlags::ToEnd | ObjectFlags::Inner, SelectMode::Extend>} },\n    { {alt('I')}, {\"select nested objects\", select_object<ObjectFlags::Nested | ObjectFlags::Inner | ObjectFlags::ToBegin | ObjectFlags::ToEnd>} },\n    { {alt('A')}, {\"select nested objects\", select_object<ObjectFlags::Nested | ObjectFlags::ToBegin | ObjectFlags::ToEnd>} },\n\n    { {alt('j')}, {\"join lines\", join_lines} },\n    { {alt('J')}, {\"join lines and select spaces\", join_lines_select_spaces} },\n\n    { {alt('k')}, {\"keep selections matching given regex\", keep<true>} },\n    { {alt('K')}, {\"keep selections not matching given regex\", keep<false>} },\n    { {'$'}, {\"pipe each selection through shell command and keep the ones whose command succeed\", keep_pipe} },\n\n    { {'<'}, {\"deindent\", deindent<true>} },\n    { {'>'}, {\"indent\", indent<false>} },\n    { {alt('>')}, {\"indent, including empty lines\", indent<true>} },\n    { {alt('<')}, {\"deindent, not including incomplete indent\", deindent<false>} },\n\n    { {ctrl('i')}, {\"jump forward in jump list\",jump<Forward>} },\n    { {Key::Tab}, {\"jump forward in jump list\",jump<Forward>} }, // legacy terminals encode <tab> / <c-i> the same way\n    { {ctrl('o')}, {\"jump backward in jump list\", jump<Backward>} },\n    { {ctrl('s')}, {\"push current selections in jump list\", push_selections} },\n\n    { {')'}, {\"rotate main selection forward\", rotate_selections<Forward>} },\n    { {'('}, {\"rotate main selection backward\", rotate_selections<Backward>} },\n    { {alt(')')}, {\"rotate selections content forward\", rotate_selections_content<Forward>} },\n    { {alt('(')}, {\"rotate selections content backward\", rotate_selections_content<Backward>} },\n\n    { {'q'}, {\"replay recorded macro\", replay_macro} },\n    { {'Q'}, {\"start or end macro recording\", start_or_end_macro_recording} },\n\n    { {'`'}, {\"convert to lower case in selections\", for_each_codepoint<to_lower>} },\n    { {'~'}, {\"convert to upper case in selections\", for_each_codepoint<to_upper>} },\n    { {alt('`')}, { \"swap case in selections\", for_each_codepoint<swap_case>} },\n\n    { {'&'}, {\"align selection cursors\", align} },\n    { {alt('&')}, {\"copy indentation\", copy_indent} },\n\n    { {'@'}, {\"convert tabs to spaces in selections\", tabs_to_spaces} },\n    { {alt('@')}, {\"convert spaces to tabs in selections\", spaces_to_tabs} },\n\n    { {'_'}, {\"trim selections\", trim_selections} },\n\n    { {'C'}, {\"duplicate selections on the lines that follow them.\", copy_selections_on_next_lines<Forward>} },\n    { {alt('C')}, {\"duplicate selections on the lines that precede them.\", copy_selections_on_next_lines<Backward>} },\n\n    { {Key::Space}, {\"user mappings\", exec_user_mappings} },\n\n    { {Key::PageUp}, {  \"scroll one page up\", scroll<Backward>} },\n    { {Key::PageDown}, {\"scroll one page down\", scroll<Forward>} },\n\n    { {ctrl('b')}, {\"scroll one page up\", scroll<Backward >} },\n    { {ctrl('f')}, {\"scroll one page down\", scroll<Forward>} },\n    { {ctrl('u')}, {\"scroll half a page up\", scroll<Backward, true>} },\n    { {ctrl('d')}, {\"scroll half a page down\", scroll<Forward, true>} },\n\n    { {'z'}, {\"restore selections from register\", restore_selections<false>} },\n    { {alt('z')}, {\"combine selections from register\", restore_selections<true>} },\n    { {'Z'}, {\"save selections to register\", save_selections<false>} },\n    { {alt('Z')}, {\"combine selections to register\", save_selections<true>} },\n\n    { {ctrl('l')}, {\"force redraw\", force_redraw} },\n};\n\nOptional<NormalCmd> get_normal_command(Key key)\n{\n    auto it = keymap.find(key);\n    if (it != keymap.end())\n        return it->value;\n    return {};\n}\n\n}\n"
  },
  {
    "path": "src/normal.hh",
    "content": "#ifndef normal_hh_INCLUDED\n#define normal_hh_INCLUDED\n\n#include \"optional.hh\"\n#include \"keys.hh\"\n#include \"string.hh\"\n#include \"exception.hh\"\n\nnamespace Kakoune\n{\n\nclass Buffer;\nclass Context;\nenum class KeymapMode : char;\n\nstruct no_selections_remaining : runtime_error\n{\n    no_selections_remaining() : runtime_error(\"no selections remaining\") {}\n};\n\nstruct NormalParams\n{\n    int count;\n    char reg;\n};\n\nstruct NormalCmd\n{\n    StringView docstring = {};\n    void (*func)(Context& context, NormalParams params) = nullptr;\n};\n\nOptional<NormalCmd> get_normal_command(Key key);\n\nstruct KeyInfo\n{\n    ConstArrayView<Key> keys;\n    StringView docstring;\n};\n\nString build_autoinfo_for_mapping(const Context& context, KeymapMode mode,\n                                  ConstArrayView<KeyInfo> built_ins);\n\nenum class PasteMode\n{\n    Append,\n    Insert,\n    Replace\n};\n\nBufferCoord paste_pos(Buffer& buffer, BufferCoord min, BufferCoord max, PasteMode mode, bool linewise);\n\n}\n\n#endif // normal_hh_INCLUDED\n"
  },
  {
    "path": "src/option.hh",
    "content": "#ifndef option_hh_INCLUDED\n#define option_hh_INCLUDED\n\n#include \"exception.hh\"\n#include \"meta.hh\"\n#include \"string.hh\"\n#include \"vector.hh\"\n\nnamespace Kakoune\n{\n\nclass String;\nenum class Quoting;\n\n// Forward declare functions that wont get found by ADL\ninline String option_to_string(int opt);\ninline String option_to_string(size_t opt);\ninline String option_to_string(bool opt);\n\n// Default fallback to single value functions\ntemplate<typename T>\ndecltype(option_from_string(Meta::Type<T>{}, StringView{}))\noption_from_strings(Meta::Type<T>, ConstArrayView<String> strs)\n{\n    if (strs.size() != 1)\n        throw runtime_error(\"expected a single value for option\");\n    return option_from_string(Meta::Type<T>{}, strs[0]);\n}\n\ntemplate<typename T>\nVector<decltype(option_to_string(std::declval<T>(), Quoting{}))>\noption_to_strings(const T& opt)\n{\n    return Vector<String>{option_to_string(opt, Quoting{})};\n}\n\ntemplate<typename T>\ndecltype(option_add(std::declval<T>(), std::declval<String>()))\noption_add_from_strings(T& opt, ConstArrayView<String> strs)\n{\n    if (strs.size() != 1)\n        throw runtime_error(\"expected a single value for option\");\n    return option_add(opt, strs[0]);\n}\n\ntemplate<typename T>\ndecltype(option_add(std::declval<T>(), std::declval<String>()))\noption_remove_from_strings(T& opt, ConstArrayView<String> strs)\n{\n    if (strs.size() != 1)\n        throw runtime_error(\"expected a single value for option\");\n    return option_remove(opt, strs[0]);\n}\n\ntemplate<typename P, typename T>\nstruct PrefixedList\n{\n    P prefix;\n    Vector<T, MemoryDomain::Options> list;\n\n    friend bool operator==(const PrefixedList& lhs, const PrefixedList& rhs) = default;\n};\n\ntemplate<typename T>\nusing TimestampedList = PrefixedList<size_t, T>;\n\n\nclass Option;\n\nclass OptionWatcher\n{\npublic:\n    virtual void on_option_changed(const Option& option) = 0;\n};\n\n}\n\n#endif // option_hh_INCLUDED\n"
  },
  {
    "path": "src/option_manager.cc",
    "content": "#include \"option_manager.hh\"\n\n#include \"assert.hh\"\n#include \"flags.hh\"\n#include \"scope.hh\"\n\nnamespace Kakoune\n{\n\nOptionDesc::OptionDesc(String name, String docstring, OptionFlags flags)\n    : m_name(std::move(name)), m_docstring(std::move(docstring)),\n    m_flags(flags) {}\n\nOption::Option(const OptionDesc& desc, OptionManager& manager)\n    : m_manager(manager), m_desc(desc) {}\n\nOptionManager::OptionManager(OptionManager& parent)\n    : m_parent(&parent)\n{\n    parent.register_watcher(*this);\n}\n\nOptionManager::~OptionManager()\n{\n    if (m_parent)\n        m_parent->unregister_watcher(*this);\n\n    kak_assert(m_watchers.empty());\n}\n\nvoid OptionManager::reparent(OptionManager& parent)\n{\n    if (m_parent)\n        m_parent->unregister_watcher(*this);\n\n    m_parent = &parent;\n    parent.register_watcher(*this);\n}\n\nvoid OptionManager::register_watcher(OptionWatcher& watcher) const\n{\n    kak_assert(not contains(m_watchers, &watcher));\n    m_watchers.push_back(&watcher);\n}\n\nvoid OptionManager::unregister_watcher(OptionWatcher& watcher) const\n{\n    auto it = find(m_watchers.begin(), m_watchers.end(), &watcher);\n    kak_assert(it != m_watchers.end());\n    m_watchers.erase(it);\n}\n\nstruct option_not_found : public runtime_error\n{\n    option_not_found(StringView name)\n        : runtime_error(format(\"option not found: '{}'. Use declare-option first\", name)) {}\n};\n\nOption& OptionManager::get_local_option(StringView name)\n{\n    auto it = m_options.find(name);\n    if (it != m_options.end())\n        return *(it->value);\n    else if (m_parent)\n    {\n        auto* clone = (*m_parent)[name].clone(*this);\n        return *m_options.insert({clone->name(), UniquePtr<Option>{clone}});\n    }\n    else\n        throw option_not_found(name);\n\n}\n\nOption& OptionManager::operator[](StringView name)\n{\n    auto it = m_options.find(name);\n    if (it != m_options.end())\n        return *it->value;\n    else if (m_parent)\n        return (*m_parent)[name];\n    else\n        throw option_not_found(name);\n}\n\nconst Option& OptionManager::operator[](StringView name) const\n{\n    return const_cast<OptionManager&>(*this)[name];\n}\n\nvoid OptionManager::unset_option(StringView name)\n{\n    kak_assert(m_parent); // cannot unset option on global manager\n    auto it = m_options.find(name);\n    if (it != m_options.end())\n    {\n        auto& parent_option = (*m_parent)[name];\n        const bool changed = not parent_option.has_same_value(*it->value);\n        GlobalScope::instance().option_registry().move_to_trash(std::move(it->value));\n        m_options.erase(name);\n        if (changed)\n            on_option_changed(parent_option);\n    }\n}\n\nvoid OptionManager::on_option_changed(const Option& option)\n{\n    // if parent option changed, but we overrided it, it's like nothing happened\n    if (&option.manager() != this and m_options.contains(option.name()))\n        return;\n\n    // The watcher list might get mutated during calls to on_option_changed\n    auto watchers = m_watchers;\n    for (auto* watcher : watchers)\n    {\n        if (contains(m_watchers, watcher)) // make sure this watcher is still alive\n            watcher->on_option_changed(option);\n    }\n}\n\nCandidateList OptionsRegistry::complete_option_name(StringView prefix,\n                                                    ByteCount cursor_pos) const\n{\n    using OptionPtr = UniquePtr<const OptionDesc>;\n    return complete(prefix, cursor_pos, m_descs |\n                    filter([](const OptionPtr& desc)\n                           { return not (desc->flags() & OptionFlags::Hidden); }) |\n                    transform(&OptionDesc::name));\n}\n\n}\n"
  },
  {
    "path": "src/option_manager.hh",
    "content": "#ifndef option_manager_hh_INCLUDED\n#define option_manager_hh_INCLUDED\n\n#include \"completion.hh\"\n#include \"exception.hh\"\n#include \"hash_map.hh\"\n#include \"option.hh\"\n#include \"option_types.hh\"\n#include \"ranges.hh\"\n#include \"vector.hh\"\n#include \"format.hh\"\n#include \"string_utils.hh\"\n#include \"unique_ptr.hh\"\n\n#include <utility>\n\nnamespace Kakoune\n{\n\nclass OptionManager;\nclass Context;\n\nenum class OptionFlags\n{\n    None   = 0,\n    Hidden = 1,\n};\n\nconstexpr bool with_bit_ops(Meta::Type<OptionFlags>) { return true; }\n\nclass OptionDesc\n{\npublic:\n    OptionDesc(String name, String docstring, OptionFlags flags);\n\n    const String& name() const { return m_name; }\n    const String& docstring() const { return m_docstring; }\n\n    OptionFlags flags() const { return m_flags; }\n\nprivate:\n    String m_name;\n    String m_docstring;\n    OptionFlags  m_flags;\n};\n\nclass Option : public UseMemoryDomain<MemoryDomain::Options>\n{\npublic:\n    virtual ~Option() = default;\n\n    template<typename T> const T& get() const;\n    template<typename T> T& get_mutable();\n    template<typename T> void set(const T& val, bool notify=true);\n    template<typename T> bool is_of_type() const;\n\n    virtual String get_as_string(Quoting quoting) const = 0;\n    virtual Vector<String> get_as_strings() const = 0;\n    virtual String get_desc_string() const = 0;\n    virtual void set_from_strings(ConstArrayView<String> strs) = 0;\n    virtual void add_from_strings(ConstArrayView<String> strs) = 0;\n    virtual void remove_from_strings(ConstArrayView<String> strs) = 0;\n    virtual void update(const Context& context) = 0;\n\n    virtual bool has_same_value(const Option& other) const = 0;\n\n    virtual Option* clone(OptionManager& manager) const = 0;\n    OptionManager& manager() const { return m_manager; }\n\n    const String& name() const { return m_desc.name(); }\n    const String& docstring() const { return m_desc.docstring(); }\n    OptionFlags flags() const { return m_desc.flags(); }\n\nprotected:\n    Option(const OptionDesc& desc, OptionManager& manager);\n\n    OptionManager& m_manager;\n    const OptionDesc& m_desc;\n};\n\nclass OptionManager final : private OptionWatcher\n{\npublic:\n    OptionManager(OptionManager& parent);\n    ~OptionManager();\n\n    void reparent(OptionManager& parent);\n\n    Option& operator[] (StringView name);\n    const Option& operator[] (StringView name) const;\n    Option& get_local_option(StringView name);\n\n    void unset_option(StringView name);\n\n    auto flatten_options() const\n    {\n        auto merge = [](auto&& first, const OptionMap& second) {\n            return concatenated(std::forward<decltype(first)>(first)\n                                | filter([&second](auto& i) { return not second.contains(i.key); }),\n                                second);\n        };\n        static const OptionMap empty;\n        auto& parent = m_parent ? m_parent->m_options : empty;\n        auto& grand_parent = (m_parent and m_parent->m_parent) ? m_parent->m_parent->m_options : empty;\n        return merge(merge(grand_parent, parent), m_options) | transform(&OptionMap::Item::value);\n    }\n\n    void register_watcher(OptionWatcher& watcher) const;\n    void unregister_watcher(OptionWatcher& watcher) const;\n\n    void on_option_changed(const Option& option) override;\nprivate:\n    OptionManager()\n        : m_parent(nullptr) {}\n    // the only one allowed to construct a root option manager\n    friend class Scope;\n    friend class OptionsRegistry;\n    using OptionMap = HashMap<StringView, UniquePtr<Option>, MemoryDomain::Options>;\n\n    OptionMap m_options;\n    OptionManager* m_parent;\n\n    mutable Vector<OptionWatcher*, MemoryDomain::Options> m_watchers;\n};\n\ntemplate<typename T>\nclass TypedOption : public Option\n{\npublic:\n    TypedOption(OptionManager& manager, const OptionDesc& desc, const T& value)\n        : Option(desc, manager), m_value(value) {}\n\n    void set(T value, bool notify = true)\n    {\n        validate(value);\n        if (m_value != value)\n        {\n            m_value = std::move(value);\n            if (notify)\n                manager().on_option_changed(*this);\n        }\n    }\n    const T& get() const { return m_value; }\n    T& get_mutable() { return m_value; }\n\n    Vector<String> get_as_strings() const override\n    {\n        return option_to_strings(m_value);\n    }\n\n    String get_as_string(Quoting quoting) const override\n    {\n        return option_to_string(m_value, quoting);\n    }\n\n    String get_desc_string() const override\n    {\n        if constexpr (std::is_same_v<int, T> or std::is_same_v<bool, T> or std::is_same_v<String, T>)\n            return option_to_string(m_value, Quoting::Raw);\n        else\n            return \"...\";\n    }\n\n    void set_from_strings(ConstArrayView<String> strs) override\n    {\n        set(option_from_strings(Meta::Type<T>{}, strs));\n    }\n\n    void add_from_strings(ConstArrayView<String> strs) override\n    {\n        if (option_add_from_strings(m_value, strs))\n            m_manager.on_option_changed(*this);\n    }\n\n    void remove_from_strings(ConstArrayView<String> strs) override\n    {\n        if (option_remove_from_strings(m_value, strs))\n            m_manager.on_option_changed(*this);\n    }\n\n    void update(const Context& context) override\n    {\n        option_update(m_value, context);\n    }\n\n    bool has_same_value(const Option& other) const override\n    {\n        return other.is_of_type<T>() and other.get<T>() == m_value;\n    }\nprivate:\n    virtual void validate(const T& value) const {}\n    T m_value;\n};\n\ntemplate<typename T, void (*validator)(const T&)>\nclass TypedCheckedOption : public TypedOption<T>\n{\n    using TypedOption<T>::TypedOption;\n\n    Option* clone(OptionManager& manager) const override\n    {\n        return new TypedCheckedOption{manager, this->m_desc, this->get()};\n    }\n\n    void validate(const T& value) const override { if (validator != nullptr) validator(value); }\n};\n\ntemplate<typename T> const T& Option::get() const\n{\n    auto* typed_opt = dynamic_cast<const TypedOption<T>*>(this);\n    if (not typed_opt)\n        throw runtime_error(format(\"option '{}' is not of type '{}'\", name(),\n                                   option_type_name(Meta::Type<T>{})));\n    return typed_opt->get();\n}\n\ntemplate<typename T> T& Option::get_mutable()\n{\n    return const_cast<T&>(get<T>());\n}\n\ntemplate<typename T> void Option::set(const T& val, bool notify)\n{\n    auto* typed_opt = dynamic_cast<TypedOption<T>*>(this);\n    if (not typed_opt)\n        throw runtime_error(format(\"option '{}' is not of type '{}'\", name(),\n                                   option_type_name(Meta::Type<T>{})));\n    return typed_opt->set(val, notify);\n}\n\ntemplate<typename T> bool Option::is_of_type() const\n{\n    return dynamic_cast<const TypedOption<T>*>(this) != nullptr;\n}\n\nclass OptionsRegistry\n{\npublic:\n    OptionsRegistry(OptionManager& global_manager) : m_global_manager(global_manager) {}\n\n    template<typename T, void (*validator)(const T&) = nullptr>\n    Option& declare_option(StringView name, StringView docstring,\n                           const T& value,\n                           OptionFlags flags = OptionFlags::None)\n    {\n        auto is_option_identifier = [](char c) {\n            return is_basic_alpha(c) or is_basic_digit(c) or c == '_';\n        };\n\n        if (not all_of(name, is_option_identifier))\n            throw runtime_error{format(\"name '{}' contains char out of [a-zA-Z0-9_]\", name)};\n\n        auto& opts = m_global_manager.m_options;\n        auto it = opts.find(name);\n        if (it != opts.end())\n        {\n            if (it->value->is_of_type<T>() and it->value->flags() == flags)\n                return *it->value;\n            throw runtime_error{format(\"option '{}' already declared with different type or flags\", name)};\n        }\n        String doc =  docstring.empty() ? format(\"[{}]\", option_type_name(Meta::Type<T>{}))\n                                        : format(\"[{}] - {}\", option_type_name(Meta::Type<T>{}), docstring);\n        m_descs.emplace_back(new OptionDesc{name.str(), std::move(doc), flags});\n        return *opts.insert({m_descs.back()->name(),\n                             make_unique_ptr<TypedCheckedOption<T, validator>>(m_global_manager, *m_descs.back(), value)});\n    }\n\n    const OptionDesc* option_desc(StringView name) const\n    {\n        auto it = find_if(m_descs,\n                          [&name](const UniquePtr<const OptionDesc>& opt)\n                          { return opt->name() == name; });\n        return it != m_descs.end() ? it->get() : nullptr;\n    }\n\n    bool option_exists(StringView name) const { return option_desc(name) != nullptr; }\n\n    CandidateList complete_option_name(StringView prefix, ByteCount cursor_pos) const;\n\n    void clear_option_trash() { m_option_trash.clear(); }\n    void move_to_trash(UniquePtr<Option>&& option) { m_option_trash.push_back(std::move(option)); }\nprivate:\n    OptionManager& m_global_manager;\n    Vector<UniquePtr<const OptionDesc>, MemoryDomain::Options> m_descs;\n    Vector<UniquePtr<Option>> m_option_trash;\n};\n\n}\n\n#endif // option_manager_hh_INCLUDED\n"
  },
  {
    "path": "src/option_types.cc",
    "content": "#include \"option_types.hh\"\n#include \"debug.hh\"\n#include \"unit_tests.hh\"\n\nnamespace Kakoune\n{\n\nUnitTest test_option_parsing{[]{\n    auto check = [](auto&& value, ConstArrayView<String> strs)\n    {\n        auto repr = option_to_strings(value);\n        kak_assert(strs == ConstArrayView<String>{repr});\n        auto parsed = option_from_strings(Meta::Type<std::remove_cvref_t<decltype(value)>>{}, strs);\n        kak_assert(parsed == value);\n    };\n\n    check(123, {\"123\"});\n    check(true, {\"true\"});\n    check(Vector<String>{\"foo\", \"bar:\", \"baz\"}, {\"foo\", \"bar:\", \"baz\"});\n    check(Vector<int>{10, 20, 30}, {\"10\", \"20\", \"30\"});\n    check(HashMap<String, int>{{\"foo\", 10}, {\"b=r\", 20}, {\"b:z\", 30}}, {\"foo=10\", \"b\\\\=r=20\", \"b:z=30\"});\n    check(DebugFlags::Keys | DebugFlags::Hooks, {\"hooks|keys\"});\n}};\n\n}\n"
  },
  {
    "path": "src/option_types.hh",
    "content": "#ifndef option_types_hh_INCLUDED\n#define option_types_hh_INCLUDED\n\n#include \"array_view.hh\"\n#include \"coord.hh\"\n#include \"exception.hh\"\n#include \"flags.hh\"\n#include \"hash_map.hh\"\n#include \"option.hh\"\n#include \"string.hh\"\n#include \"string_utils.hh\"\n#include \"format.hh\"\n#include \"units.hh\"\n#include \"ranges.hh\"\n\n#include <tuple>\n\nnamespace Kakoune\n{\n\ntemplate<typename T>\nString option_to_string(const T& value, Quoting)\n    requires std::is_same_v<decltype(option_to_string(std::declval<T>())), String>\n{\n    return option_to_string(value);\n}\n\ntemplate<typename T>\nconstexpr decltype(T::option_type_name) option_type_name(Meta::Type<T>)\n{\n    return T::option_type_name;\n}\n\ntemplate<typename Enum> requires std::is_enum_v<Enum>\nString option_type_name(Meta::Type<Enum>)\n{\n    return format(\"{}({})\", with_bit_ops(Meta::Type<Enum>{}) ? \"flags\" : \"enum\",\n                  join(enum_desc(Meta::Type<Enum>{}) |\n                       transform(&EnumDesc<Enum>::name), '|'));\n}\n\ninline String option_to_string(int opt) { return to_string(opt); }\ninline int option_from_string(Meta::Type<int>, StringView str) { return str_to_int(str); }\ninline bool option_add(int& opt, StringView str)\n{\n    auto val = str_to_int(str);\n    opt += val;\n    return val != 0;\n}\ninline bool option_remove(int& opt, StringView str)\n{\n    auto val = str_to_int(str);\n    opt -= val;\n    return val != 0;\n}\nconstexpr StringView option_type_name(Meta::Type<int>) { return \"int\"; }\n\ninline String option_to_string(size_t opt) { return to_string(opt); }\ninline size_t option_from_string(Meta::Type<size_t>, StringView str) { return str_to_int(str); }\n\ninline String option_to_string(bool opt) { return opt ? \"true\" : \"false\"; }\ninline bool option_from_string(Meta::Type<bool>, StringView str)\n{\n    if (str == \"true\" or str == \"yes\")\n        return true;\n    else if (str == \"false\" or str == \"no\")\n        return false;\n    else\n        throw runtime_error(\"boolean values are either true, yes, false or no\");\n}\nconstexpr StringView option_type_name(Meta::Type<bool>) { return \"bool\"; }\n\ninline String option_to_string(Codepoint opt, Quoting quoting) { return quoter(quoting)(to_string(opt)); }\ninline Codepoint option_from_string(Meta::Type<Codepoint>, StringView str)\n{\n    if (str.char_length() != 1)\n        throw runtime_error{format(\"'{}' is not a single codepoint\", str)};\n    return str[0_char];\n}\nconstexpr StringView option_type_name(Meta::Type<Codepoint>) { return \"codepoint\"; }\n\ntemplate<typename T, MemoryDomain domain>\nVector<String> option_to_strings(const Vector<T, domain>& opt)\n{\n    return opt | transform([](const T& t) { return option_to_string(t, Quoting::Raw); }) | gather<Vector<String>>();\n}\n\ntemplate<typename T, MemoryDomain domain>\nString option_to_string(const Vector<T, domain>& opt, Quoting quoting)\n{\n    return join(opt | transform([=](const T& t) { return option_to_string(t, quoting); }), ' ', false);\n}\n\ntemplate<typename T, MemoryDomain domain>\nvoid option_list_postprocess(Vector<T, domain>& opt)\n{}\n\ntemplate<typename T, MemoryDomain domain>\nVector<T, domain> option_from_strings(Meta::Type<Vector<T, domain>>, ConstArrayView<String> strs)\n{\n    auto res =  strs | transform([](auto&& s) { return option_from_string(Meta::Type<T>{}, s); })\n                     | gather<Vector<T, domain>>();\n    option_list_postprocess(res);\n    return res;\n}\n\ntemplate<typename T, MemoryDomain domain>\nbool option_add_from_strings(Vector<T, domain>& opt, ConstArrayView<String> strs)\n{\n    auto vec = option_from_strings(Meta::Type<Vector<T, domain>>{}, strs);\n    opt.insert(opt.end(),\n               std::make_move_iterator(vec.begin()),\n               std::make_move_iterator(vec.end()));\n    option_list_postprocess(opt);\n    return not vec.empty();\n}\n\ntemplate<typename T, MemoryDomain domain>\nbool option_remove_from_strings(Vector<T, domain>& opt, ConstArrayView<String> strs)\n{\n    bool did_remove = false;\n    for (auto&& val : strs | transform([](auto&& s) { return option_from_string(Meta::Type<T>{}, s); }))\n    {\n        auto it = find(opt, val);\n        if (it == opt.end())\n            continue;\n        opt.erase(it);\n        did_remove = true;\n    }\n    return did_remove;\n}\n\ntemplate<typename T, MemoryDomain D>\nString option_type_name(Meta::Type<Vector<T, D>>)\n{\n    return option_type_name(Meta::Type<T>{}) + \"-list\"_sv;\n}\n\ntemplate<typename Key, typename Value, MemoryDomain domain>\nVector<String> option_to_strings(const HashMap<Key, Value, domain>& opt)\n{\n    return opt | transform([](auto&& item) {\n        return format(\"{}={}\",\n                      escape(option_to_string(item.key, Quoting::Raw), '=', '\\\\'),\n                      escape(option_to_string(item.value, Quoting::Raw), '=', '\\\\'));\n    }) | gather<Vector<String>>();\n}\n\ntemplate<typename Key, typename Value, MemoryDomain domain>\nString option_to_string(const HashMap<Key, Value, domain>& opt, Quoting quoting)\n{\n    return join(opt | transform([=](auto&& item) {\n        return quoter(quoting)(\n            format(\"{}={}\",\n                   escape(option_to_string(item.key, Quoting::Raw), '=', '\\\\'),\n                   escape(option_to_string(item.value, Quoting::Raw), '=', '\\\\')));\n    }), ' ', false);\n}\n\ntemplate<typename Key, typename Value, MemoryDomain domain>\nbool option_add_from_strings(HashMap<Key, Value, domain>& opt, ConstArrayView<String> strs)\n{\n    struct error : runtime_error { error(size_t) : runtime_error{\"map option expects key=value\"} {} };\n\n    bool changed = false;\n    for (auto&& str : strs)\n    {\n        auto key_value = str | split<StringView>('=', '\\\\')\n                             | transform(unescape<'=', '\\\\'>)\n                             | static_gather<error, 2>();\n\n        opt[option_from_string(Meta::Type<Key>{}, key_value[0])] = option_from_string(Meta::Type<Value>{}, key_value[1]);\n        changed = true;\n    }\n    return changed;\n}\n\ntemplate<typename Key, typename Value, MemoryDomain domain>\nbool option_remove_from_strings(HashMap<Key, Value, domain>& opt, ConstArrayView<String> strs)\n{\n    struct error : runtime_error { error(size_t) : runtime_error{\"map option expects key=value\"} {} };\n\n    bool changed = false;\n    for (auto&& str : strs)\n    {\n        auto key_value = str | split<StringView>('=', '\\\\')\n                             | transform(unescape<'=', '\\\\'>)\n                             | static_gather<error, 2>();\n\n        if (auto it = opt.find(key_value[0]); it != opt.end() and (key_value[1].empty() or key_value[1] == it->value))\n        {\n            opt.remove(it->key);\n            changed = true;\n        }\n    }\n    return changed;\n}\n\ntemplate<typename Key, typename Value, MemoryDomain domain>\nHashMap<Key, Value, domain> option_from_strings(Meta::Type<HashMap<Key, Value, domain>>, ConstArrayView<String> str)\n{\n    HashMap<Key, Value, domain> res;\n    option_add_from_strings(res, str);\n    return res;\n}\n\ntemplate<typename K, typename V, MemoryDomain D>\nString option_type_name(Meta::Type<HashMap<K, V, D>>)\n{\n    return format(\"{}-to-{}-map\", option_type_name(Meta::Type<K>{}),\n                  option_type_name(Meta::Type<V>{}));\n}\n\nconstexpr char tuple_separator = '|';\n\ntemplate<typename... Types, size_t... I>\nString option_to_string_impl(Quoting quoting, const std::tuple<Types...>& opt, std::index_sequence<I...>)\n{\n    return quoter(quoting)(join(make_array({option_to_string(std::get<I>(opt), Quoting::Raw)...}), tuple_separator));\n}\n\ntemplate<typename... Types>\nString option_to_string(const std::tuple<Types...>& opt, Quoting quoting)\n{\n    return option_to_string_impl(quoting, opt, std::make_index_sequence<sizeof...(Types)>());\n}\n\ntemplate<typename... Types, size_t... I>\nstd::tuple<Types...> option_from_string_impl(Meta::Type<std::tuple<Types...>>, StringView str,\n                                             std::index_sequence<I...>)\n{\n    struct error : runtime_error\n    {\n        error(size_t i) : runtime_error{i < sizeof...(Types) ?\n                                          \"not enough elements in tuple\"\n                                        : \"too many elements in tuple\"} {}\n    };\n    auto elems = str | split<StringView>(tuple_separator, '\\\\')\n                     | transform(unescape<tuple_separator, '\\\\'>)\n                     | static_gather<error, sizeof...(Types)>();\n    return std::tuple<Types...>{option_from_string(Meta::Type<Types>{}, elems[I])...};\n}\n\ntemplate<typename... Types>\nstd::tuple<Types...> option_from_string(Meta::Type<std::tuple<Types...>>, StringView str)\n{\n    return option_from_string_impl(Meta::Type<std::tuple<Types...>>{}, str,\n                                   std::make_index_sequence<sizeof...(Types)>());\n}\n\ntemplate<typename RealType, typename ValueType>\ninline String option_to_string(const StronglyTypedNumber<RealType, ValueType>& opt)\n{\n    return to_string(opt);\n}\n\ntemplate<typename Number>\n    requires std::is_base_of_v<StronglyTypedNumber<Number, int>, Number>\nNumber option_from_string(Meta::Type<Number>, StringView str)\n{\n     return Number{str_to_int(str)};\n}\n\ntemplate<typename RealType, typename ValueType>\ninline bool option_add(StronglyTypedNumber<RealType, ValueType>& opt, StringView str)\n{\n    int val = str_to_int(str);\n    opt += val;\n    return val != 0;\n}\n\nstruct WorstMatch { template<typename T> WorstMatch(T&&) {} };\n\ninline bool option_add(WorstMatch, StringView)\n{\n    throw runtime_error(\"no add operation supported for this option type\");\n}\n\ninline bool option_remove(WorstMatch, StringView)\n{\n    throw runtime_error(\"no remove operation supported for this option type\");\n}\n\nclass Context;\n\ninline void option_update(WorstMatch, const Context&)\n{\n    throw runtime_error(\"no update operation supported for this option type\");\n}\n\ntemplate<typename Coord>\n    requires std::is_base_of_v<LineAndColumn<Coord, decltype(Coord::line), decltype(Coord::column)>, Coord>\nCoord option_from_string(Meta::Type<Coord>, StringView str)\n{\n    struct error : runtime_error { error(size_t) : runtime_error{\"expected <line>,<column>\"} {} };\n    auto vals = str | split<StringView>(',')\n                    | static_gather<error, 2>();\n    return {str_to_int(vals[0]), str_to_int(vals[1])};\n}\n\ntemplate<typename EffectiveType, typename LineType, typename ColumnType>\ninline String option_to_string(const LineAndColumn<EffectiveType, LineType, ColumnType>& opt)\n{\n    return format(\"{},{}\", opt.line, opt.column);\n}\n\ntemplate<DescribedEnum Flags> requires WithBitOps<Flags>\nString option_to_string(Flags flags)\n{\n    constexpr auto desc = enum_desc(Meta::Type<Flags>{});\n    String res;\n    for (int i = 0; i < desc.size(); ++i)\n    {\n        if (not (flags & desc[i].value))\n            continue;\n        if (not res.empty())\n            res += \"|\";\n        res += desc[i].name;\n    }\n    return res;\n}\n\ntemplate<DescribedEnum Enum> requires (not WithBitOps<Enum>)\nString option_to_string(Enum e)\n{\n    constexpr auto desc = enum_desc(Meta::Type<Enum>{});\n    auto it = find_if(desc, [e](const EnumDesc<Enum>& d) { return d.value == e; });\n    if (it != desc.end())\n        return it->name.str();\n    kak_assert(false);\n    return {};\n}\n\ntemplate<DescribedEnum Flags> requires WithBitOps<Flags>\nFlags option_from_string(Meta::Type<Flags>, StringView str)\n{\n    constexpr auto desc = enum_desc(Meta::Type<Flags>{});\n    Flags flags{};\n    for (auto s : str | split<StringView>('|'))\n    {\n        auto it = find_if(desc, [s](const EnumDesc<Flags>& d) { return d.name == s; });\n        if (it == desc.end())\n            throw runtime_error(format(\"invalid flag value '{}'\", s));\n        flags |= it->value;\n    }\n    return flags;\n}\n\ntemplate<DescribedEnum Enum> requires (not WithBitOps<Enum>)\nEnum option_from_string(Meta::Type<Enum>, StringView str)\n{\n    constexpr auto desc = enum_desc(Meta::Type<Enum>{});\n    auto it = find_if(desc, [str](const EnumDesc<Enum>& d) { return d.name == str; });\n    if (it == desc.end())\n        throw runtime_error(format(\"invalid enum value '{}'\", str));\n    return it->value;\n}\n\ntemplate<DescribedEnum Flags> requires WithBitOps<Flags>\nbool option_add(Flags& opt, StringView str)\n{\n    const Flags old = opt;\n    opt |= option_from_string(Meta::Type<Flags>{}, str);\n    return opt != old;\n}\n\ntemplate<DescribedEnum Flags> requires WithBitOps<Flags>\nbool option_remove(Flags& opt, StringView str)\n{\n    const Flags old = opt;\n    opt &= ~option_from_string(Meta::Type<Flags>{}, str);\n    return opt != old;\n}\n\ntemplate<typename P, typename T>\ninline Vector<String> option_to_strings(const PrefixedList<P, T>& opt)\n{\n    Vector<String> res{option_to_string(opt.prefix, Quoting::Raw)};\n    auto list = option_to_strings(opt.list);\n    res.insert(res.end(), std::make_move_iterator(list.begin()), std::make_move_iterator(list.end()));\n    return res;\n}\n\ntemplate<typename P, typename T>\ninline String option_to_string(const PrefixedList<P, T>& opt, Quoting quoting)\n{\n    return option_to_string(opt.prefix, quoting) + \" \" + option_to_string(opt.list, quoting);\n}\n\ntemplate<typename P, typename T>\ninline PrefixedList<P, T> option_from_strings(Meta::Type<PrefixedList<P, T>>, ConstArrayView<String> strs)\n{\n    if (strs.empty())\n        return {{}, {}};\n\n    return {option_from_string(Meta::Type<P>{}, strs[0]),\n            option_from_strings(Meta::Type<Vector<T, MemoryDomain::Options>>{}, strs.subrange(1))};\n}\n\ntemplate<typename P, typename T>\ninline bool option_add_from_strings(PrefixedList<P, T>& opt, ConstArrayView<String> str)\n{\n    return option_add_from_strings(opt.list, str);\n}\n\ntemplate<typename P, typename T>\ninline bool option_remove_from_strings(PrefixedList<P, T>& opt, ConstArrayView<String> str)\n{\n    return option_remove_from_strings(opt.list, str);\n}\n\n}\n\n#endif // option_types_hh_INCLUDED\n"
  },
  {
    "path": "src/optional.hh",
    "content": "#ifndef optional_hh_INCLUDED\n#define optional_hh_INCLUDED\n\n#include \"assert.hh\"\n\n#include <utility>\n\nnamespace Kakoune\n{\n\ntemplate<typename T>\nstruct Optional\n{\npublic:\n    constexpr Optional() : m_valid{false} {}\n    Optional(const T& other) : m_valid{true} { new (&m_value) T(other); }\n    Optional(T&& other) : m_valid{true} { new (&m_value) T(std::move(other)); }\n\n    Optional(const Optional& other)\n        : m_valid{other.m_valid}\n    {\n        if (m_valid)\n            new (&m_value) T(other.m_value);\n    }\n\n    Optional(Optional&& other)\n        noexcept(noexcept(new (nullptr) T(std::move(other.m_value))))\n        : m_valid{other.m_valid}\n    {\n        if (m_valid)\n            new (&m_value) T(std::move(other.m_value));\n    }\n\n    Optional& operator=(const Optional& other)\n    {\n        destruct_ifn();\n        if ((m_valid = other.m_valid))\n            new (&m_value) T(other.m_value);\n        return *this;\n    }\n\n    Optional& operator=(Optional&& other)\n    {\n        destruct_ifn();\n        if ((m_valid = other.m_valid))\n            new (&m_value) T(std::move(other.m_value));\n        return *this;\n    }\n\n    ~Optional() { destruct_ifn(); }\n\n    constexpr explicit operator bool() const noexcept { return m_valid; }\n\n    bool operator==(const Optional& other) const\n    {\n        return m_valid == other.m_valid and\n               (not m_valid or m_value == other.m_value);\n    }\n\n    template<typename... Args>\n    T& emplace(Args&&... args)\n    {\n        destruct_ifn();\n        new (&m_value) T{std::forward<Args>(args)...};\n        m_valid = true;\n        return m_value;\n    }\n\n    T& operator*() &\n    {\n        kak_assert(m_valid);\n        return m_value;\n    }\n\n    T&& operator*() &&\n    {\n        kak_assert(m_valid);\n        return std::move(m_value);\n    }\n\n    const T& operator*() const & { return *const_cast<Optional&>(*this); }\n    const T& operator*() const && { return *const_cast<Optional&>(*this); }\n\n    T* operator->()\n    {\n        kak_assert(m_valid);\n        return &m_value;\n    }\n    const T* operator->() const { return const_cast<Optional&>(*this).operator->(); }\n\n    template<typename U> struct DecayOptionalImpl { using Type = U; };\n    template<typename U> struct DecayOptionalImpl<Optional<U>> { using Type = typename DecayOptionalImpl<U>::Type; };\n    template<typename U> using DecayOptional = typename DecayOptionalImpl<U>::Type;\n\n    template<typename F>\n    auto map(F f) -> Optional<DecayOptional<decltype(f(std::declval<T>()))>>\n    {\n        if (not m_valid)\n            return {};\n        return {f(m_value)};\n    }\n\n    template<typename U>\n    auto cast() const -> Optional<U>\n    {\n        if (not m_valid)\n            return {};\n        return {(U)m_value};\n    }\n\n    template<typename U>\n    T value_or(U&& fallback) const { return m_valid ? m_value : T{std::forward<U>(fallback)}; }\n\n    template<typename U>\n    T value_or_compute(U&& compute_func) const { return m_valid ? m_value : compute_func(); }\n\n    void reset() { destruct_ifn(); m_valid = false; }\n\nprivate:\n    void destruct_ifn() { if (m_valid) m_value.~T(); }\n\n    struct Empty {};\n    union\n    {\n        Empty m_empty; // disable default construction of value\n        T m_value;\n    };\n    bool m_valid;\n};\n\n}\n\n#endif // optional_hh_INCLUDED\n"
  },
  {
    "path": "src/parameters_parser.cc",
    "content": "#include \"parameters_parser.hh\"\n\n#include \"ranges.hh\"\n#include \"flags.hh\"\n\nnamespace Kakoune\n{\n\nString generate_switches_doc(const SwitchMap& switches)\n{\n    String res;\n    if (switches.empty())\n        return res;\n\n    auto switch_len = [](auto& sw) { return sw.key.column_length() + (sw.value.arg_completer ? 5 : 0); };\n    auto switches_len = switches | transform(switch_len);\n    const ColumnCount maxlen = *std::max_element(switches_len.begin(), switches_len.end());\n\n    for (auto& sw : switches) {\n        res += format(\"-{} {}{}{}\\n\",\n                      sw.key,\n                      sw.value.arg_completer ? \"<arg>\" : \"\",\n                      String{' ', maxlen - switch_len(sw) + 1},\n                      sw.value.description);\n    }\n    return res;\n}\n\nParametersParser::ParametersParser(ParameterList params, const ParameterDesc& desc, bool ignore_errors)\n    : m_params(params)\n{\n    const bool switches_only_at_start = desc.flags & ParameterDesc::Flags::SwitchesOnlyAtStart;\n    const bool ignore_unknown_switches = desc.flags & ParameterDesc::Flags::IgnoreUnknownSwitches;\n    bool only_pos = desc.flags & ParameterDesc::Flags::SwitchesAsPositional;\n\n    Vector<bool> switch_seen(desc.switches.size(), false);\n    for (size_t i = 0; i < params.size(); ++i)\n    {\n        if (not only_pos and not ignore_unknown_switches and params[i] == \"--\")\n        {\n            m_state = State::Switch;\n            only_pos = true;\n        }\n        else if (not only_pos and not params[i].empty() and params[i][0_byte] == '-')\n        {\n            StringView switch_name = params[i].substr(1_byte);\n            auto it = desc.switches.find(switch_name);\n            m_state = it == desc.switches.end() and ignore_unknown_switches ?\n                        State::Positional : State::Switch;\n            if (it == desc.switches.end())\n            {\n                if (ignore_unknown_switches)\n                {\n                    m_positional_indices.push_back(i);\n                    if (switches_only_at_start)\n                        only_pos = true;\n                    continue;\n                }\n                if (ignore_errors)\n                    continue;\n                throw unknown_option(params[i]);\n            }\n\n            auto switch_index = it - desc.switches.begin();\n            if (switch_seen[switch_index])\n            {\n                if (ignore_errors)\n                    continue;\n                throw runtime_error{format(\"switch '-{}' specified more than once\", it->key)};\n            }\n            switch_seen[switch_index] = true;\n\n            if (it->value.arg_completer)\n            {\n               if (++i == params.size())\n               {\n                   if (ignore_errors)\n                       continue;\n                   throw missing_option_value(it->key);\n               }\n               m_state = State::SwitchArgument;\n            }\n\n            m_switches[switch_name.str()] = it->value.arg_completer ? params[i] : StringView{};\n        }\n        else // positional\n        {\n            m_state = State::Positional;\n            if (switches_only_at_start)\n                only_pos = true;\n            m_positional_indices.push_back(i);\n        }\n    }\n    size_t count = m_positional_indices.size();\n    if (not ignore_errors and (count > desc.max_positionals or count < desc.min_positionals))\n        throw wrong_argument_count();\n}\n\nOptional<StringView> ParametersParser::get_switch(StringView name) const\n{\n    auto it = m_switches.find(name);\n    return it == m_switches.end() ? Optional<StringView>{}\n                                  : Optional<StringView>{it->value};\n}\n\n}\n"
  },
  {
    "path": "src/parameters_parser.hh",
    "content": "#ifndef parameters_parser_hh_INCLUDED\n#define parameters_parser_hh_INCLUDED\n\n#include \"exception.hh\"\n#include \"hash_map.hh\"\n#include \"meta.hh\"\n#include \"array_view.hh\"\n#include \"optional.hh\"\n#include \"string.hh\"\n#include \"format.hh\"\n#include \"function.hh\"\n\nnamespace Kakoune\n{\n\nusing ParameterList = ConstArrayView<String>;\n\nstruct parameter_error : public runtime_error\n{\n    using runtime_error::runtime_error;\n};\n\nstruct unknown_option : public parameter_error\n{\n    unknown_option(StringView name)\n        : parameter_error(format(\"unknown option '{}'\", name)) {}\n};\n\nstruct missing_option_value: public parameter_error\n{\n    missing_option_value(StringView name)\n        : parameter_error(format(\"missing value for option '{}'\", name)) {}\n};\n\nstruct wrong_argument_count : public parameter_error\n{\n    wrong_argument_count() : parameter_error(\"wrong argument count\") {}\n};\n\nclass Context;\nstruct Completions;\nusing ArgCompleter = Function<Completions (const Context&, StringView, ByteCount)>;\n\nstruct SwitchDesc\n{\n    Optional<ArgCompleter> arg_completer;\n    String description;\n};\n\nusing SwitchMap = HashMap<String, SwitchDesc, MemoryDomain::Commands>;\n\nString generate_switches_doc(const SwitchMap& opts);\n\nstruct ParameterDesc\n{\n    enum class Flags\n    {\n        None = 0,\n        SwitchesOnlyAtStart   = 0b0001,\n        SwitchesAsPositional  = 0b0010,\n        IgnoreUnknownSwitches = 0b0100\n    };\n    friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; }\n\n    SwitchMap switches;\n    Flags flags = Flags::None;\n    size_t min_positionals = 0;\n    size_t max_positionals = -1;\n};\n\n// ParametersParser provides tools to parse command parameters.\n// There are 3 types of parameters:\n//  * unnamed options, which are accessed by position (ignoring named ones)\n//  * named boolean options, which are enabled using '-name' syntax\n//  * named string options,  which are defined using '-name value' syntax\nstruct ParametersParser\n{\n    // the options defines named options, if they map to true, then\n    // they are understood as string options, else they are understood as\n    // boolean option.\n    ParametersParser(ParameterList params, const ParameterDesc& desc, bool ignore_errors = false);\n\n    enum class State {\n        Switch,\n        SwitchArgument,\n        Positional,\n    };\n\n    // Return a valid optional if the switch was given, with\n    // a non empty StringView value if the switch took an argument.\n    Optional<StringView> get_switch(StringView name) const;\n\n    struct iterator\n    {\n        using difference_type = ptrdiff_t;\n        using value_type = String;\n        using pointer = String*;\n        using reference = String&;\n        using iterator_category = std::forward_iterator_tag;\n\n        iterator(const ParametersParser& parser, size_t index)\n            : m_parser(parser), m_index(index) {}\n\n        const String& operator*() const { return m_parser[m_index]; }\n        const String* operator->() const { return &m_parser[m_index]; }\n\n        iterator& operator++() { ++m_index; return *this; }\n        iterator operator++(int) { auto copy = *this; ++m_index; return copy; }\n\n        bool operator==(const iterator& other) const\n        {\n            kak_assert(&m_parser == &other.m_parser);\n            return m_index == other.m_index;\n        }\n\n    private:\n        const ParametersParser& m_parser;\n        size_t                  m_index;\n    };\n\n    // positional parameters count\n    size_t positional_count() const { return m_positional_indices.size(); }\n\n    // access positional parameter by index\n    const String& operator[] (size_t index) const\n    {\n        kak_assert(index < positional_count());\n        return m_params[m_positional_indices[index]];\n    }\n\n    ConstArrayView<String> positionals_from(size_t first) const\n    {\n        // kak_assert(m_desc.flags & (ParameterDesc::Flags::SwitchesOnlyAtStart | ParameterDesc::Flags::SwitchesAsPositional));\n        return m_params.subrange(first < m_positional_indices.size() ? m_positional_indices[first] : -1);\n    }\n\n    iterator begin() const { return iterator(*this, 0); }\n    iterator end() const { return iterator(*this, m_positional_indices.size()); }\n\n    State state() const { return *m_state; }\n\nprivate:\n    ParameterList m_params;\n    Vector<size_t, MemoryDomain::Commands> m_positional_indices;\n    HashMap<String, StringView> m_switches;\n    Optional<State> m_state;\n};\n\n}\n\n#endif // parameters_parser_hh_INCLUDED\n"
  },
  {
    "path": "src/profile.hh",
    "content": "#ifndef profile_hh_INCLUDED\n#define profile_hh_INCLUDED\n\n#include \"clock.hh\"\n#include \"context.hh\"\n#include \"debug.hh\"\n\nnamespace Kakoune\n{\n\ntemplate<typename Callback>\nclass ProfileScope\n{\npublic:\n    ProfileScope(const DebugFlags debug_flags, Callback&& callback, bool active = true)\n      : m_active{active and debug_flags & DebugFlags::Profile},\n        m_start_time(m_active ? Clock::now() : Clock::time_point{}),\n        m_callback{std::move(callback)}\n    {}\n\n    ~ProfileScope()\n    {\n        if (m_active)\n            m_callback(std::chrono::duration_cast<std::chrono::microseconds>(Clock::now() - m_start_time));\n    }\n\nprivate:\n    bool m_active;\n    Clock::time_point m_start_time;\n    Callback m_callback;\n};\n\n}\n\n#endif // profile_hh_INCLUDED\n"
  },
  {
    "path": "src/range.hh",
    "content": "#ifndef range_hh_INCLUDED\n#define range_hh_INCLUDED\n\n#include <cstddef>\n\nnamespace Kakoune\n{\n\ntemplate<typename T>\nstruct Range\n{\n    T begin;\n    T end;\n\n    friend bool operator==(const Range& lhs, const Range& rhs) = default;\n\n    friend size_t hash_value(const Range& range)\n    {\n        return hash_values(range.begin, range.end);\n    }\n\n    bool empty() const { return begin == end; }\n};\n\n}\n\n#endif // range_hh_INCLUDED\n"
  },
  {
    "path": "src/ranges.cc",
    "content": "#include \"ranges.hh\"\n#include \"unit_tests.hh\"\n#include \"string.hh\"\n#include \"string_utils.hh\"\n\nnamespace Kakoune\n{\n\nUnitTest test_ranges{[] {\n    using Strs = ConstArrayView<StringView>;\n    auto check_equal = [](auto&& container, auto&& expected) {\n        kak_assert(std::equal(container.begin(), container.end(), expected.begin(), expected.end()));\n    };\n    check_equal(\"a,b,c\"_sv | split<StringView>(','), Strs{\"a\", \"b\", \"c\"});\n    check_equal(\",b,c\"_sv  | split<StringView>(','), Strs{\"\", \"b\", \"c\"});\n    check_equal(\",b,\"_sv   | split<StringView>(','), Strs{\"\", \"b\", \"\"});\n    check_equal(\",\"_sv     | split<StringView>(','), Strs{\"\", \"\"});\n    check_equal(\"\"_sv      | split<StringView>(','), Strs{});\n\n    check_equal(\"a,b,c,\"_sv | split_after<StringView>(','), Strs{\"a,\", \"b,\", \"c,\"});\n    check_equal(\"a,b,c\"_sv  | split_after<StringView>(','), Strs{\"a,\", \"b,\", \"c\"});\n\n    check_equal(R\"(a\\,,\\,b,\\,)\"_sv | split<StringView>(',', '\\\\')\n                                   | transform(unescape<',', '\\\\'>), Strs{\"a,\", \",b\", \",\"});\n    check_equal(R\"(\\,\\,)\"_sv | split<StringView>(',', '\\\\')\n                             | transform(unescape<',', '\\\\'>), Strs{\",,\"});\n    check_equal(R\"(\\\\,\\\\,)\"_sv | split<StringView>(',', '\\\\')\n                               | transform(unescape<',', '\\\\'>), Strs{R\"(\\)\", R\"(\\)\", \"\"});\n\n    check_equal(Array{\"\"_sv, \"abc\"_sv, \"\"_sv, \"def\"_sv, \"\"_sv} | flatten(), \"abcdef\"_sv);\n    check_equal(Vector<StringView>{\"\", \"\"} | flatten(), \"\"_sv);\n    check_equal(Vector<StringView>{} | flatten(), \"\"_sv);\n}};\n\n}\n"
  },
  {
    "path": "src/ranges.hh",
    "content": "#ifndef ranges_hh_INCLUDED\n#define ranges_hh_INCLUDED\n\n#include <algorithm>\n#include <utility>\n#include <iterator>\n#include <numeric>\n\n#include \"array.hh\"\n\nnamespace Kakoune\n{\n\ntemplate<typename Func> struct ViewFactory { Func func; };\n\ntemplate<typename Func>\nViewFactory(Func&&) -> ViewFactory<std::remove_cvref_t<Func>>;\n\ntemplate<typename Range, typename Func>\ndecltype(auto) operator| (Range&& range, ViewFactory<Func> factory)\n{\n    return factory.func(std::forward<Range>(range));\n}\n\ntemplate<typename Range>\nstruct DecayRangeImpl { using type = std::remove_cvref_t<Range>; };\n\ntemplate<typename Range>\nstruct DecayRangeImpl<Range&> { using type = Range&; };\n\ntemplate<typename Range>\nusing DecayRange = typename DecayRangeImpl<Range>::type;\n\ntemplate<typename Range>\nstruct RangeHolderImpl { using type = std::remove_cvref_t<Range>; };\n\ntemplate<typename Range>\nstruct RangeHolderImpl<Range&> {\n    struct type\n    {\n        Range* range{};\n\n        decltype(auto) begin() { return std::begin(*range); }\n        decltype(auto) end() { return std::end(*range); }\n\n        type& operator=(Range& r) { range = &r; return *this; }\n        operator Range&() { return *range; }\n    };\n};\n\ntemplate<typename Range>\nusing RangeHolder = typename RangeHolderImpl<Range>::type;\n\ntemplate<typename Range>\nstruct ReverseView\n{\n    decltype(auto) begin() { return m_range.rbegin(); }\n    decltype(auto) end()   { return m_range.rend(); }\n    decltype(auto) rbegin() { return m_range.begin(); }\n    decltype(auto) rend()   { return m_range.end(); }\n    decltype(auto) begin() const { return m_range.rbegin(); }\n    decltype(auto) end() const  { return m_range.rend(); }\n    decltype(auto) rbegin() const { return m_range.begin(); }\n    decltype(auto) rend() const  { return m_range.end(); }\n\n    Range m_range;\n};\n\nconstexpr auto reverse()\n{\n    return ViewFactory{[](auto&& range) {\n        using Range = decltype(range);\n        return ReverseView<DecayRange<Range>>{std::forward<Range>(range)};\n    }};\n}\n\ntemplate<typename Range>\nusing IteratorOf = decltype(std::begin(std::declval<Range>()));\n\ntemplate<typename Range>\nusing ValueOf = decltype(*std::declval<IteratorOf<Range>>());\n\ntemplate<typename Range>\nstruct SkipView\n{\n    auto begin() const { return std::next(std::begin(m_range), m_skip_count); }\n    auto end()   const { return std::end(m_range); }\n\n    Range m_range;\n    size_t m_skip_count;\n};\n\nconstexpr auto skip(size_t count)\n{\n    return ViewFactory{[count](auto&& range) {\n        using Range = decltype(range);\n        return SkipView<DecayRange<Range>>{std::forward<Range>(range), count};\n    }};\n}\n\ntemplate<typename Range>\nstruct DropView\n{\n    auto begin() const { return std::begin(m_range); }\n    auto end()   const { return std::end(m_range) - m_drop_count; }\n\n    Range m_range;\n    size_t m_drop_count;\n};\n\nconstexpr auto drop(size_t count)\n{\n    return ViewFactory{[count](auto&& range) {\n        using Range = decltype(range);\n        return DropView<DecayRange<Range>>{std::forward<Range>(range), count};\n    }};\n}\n\ntemplate<typename Range, typename Filter>\nstruct FilterView\n{\n    using RangeIt = IteratorOf<Range>;\n\n    struct Iterator\n    {\n        using difference_type = ptrdiff_t;\n        using value_type = typename std::iterator_traits<RangeIt>::value_type;\n        using pointer = value_type*;\n        using reference = value_type&;\n        using iterator_category = std::forward_iterator_tag;\n\n        Iterator(Filter& filter, RangeIt it, RangeIt end)\n            : m_it{std::move(it)}, m_end{std::move(end)}, m_filter{&filter}\n        {\n            do_filter();\n        }\n\n        decltype(auto) operator*() { return *m_it; }\n        Iterator& operator++() { ++m_it; do_filter(); return *this; }\n        Iterator operator++(int) { auto copy = *this; ++(*this); return copy; }\n\n        friend bool operator==(const Iterator& lhs, const Iterator& rhs)\n        {\n            return lhs.m_it == rhs.m_it;\n        }\n\n        const RangeIt& base() const { return m_it; }\n\n    private:\n        void do_filter()\n        {\n            while (m_it != m_end and not (*m_filter)(*m_it))\n                ++m_it;\n        }\n\n        RangeIt m_it;\n        RangeIt m_end;\n        Filter* m_filter;\n    };\n\n    Iterator begin() const { return {m_filter, std::begin(m_range), std::end(m_range)}; }\n    Iterator end()   const { return {m_filter, std::end(m_range), std::end(m_range)}; }\n\n    Range m_range;\n    mutable Filter m_filter;\n};\n\ntemplate<typename Filter>\nconstexpr auto filter(Filter f)\n{\n    return ViewFactory{[f = std::move(f)](auto&& range) {\n        using Range = decltype(range);\n        return FilterView<DecayRange<Range>, Filter>{std::forward<Range>(range), std::move(f)};\n    }};\n}\n\ntemplate<typename Range>\nstruct EnumerateView\n{\n    using RangeIt = IteratorOf<Range>;\n\n    struct Iterator\n    {\n        using difference_type = ptrdiff_t;\n        using value_type = typename std::iterator_traits<RangeIt>::value_type;\n        using pointer = value_type*;\n        using reference = value_type&;\n        using iterator_category = std::forward_iterator_tag;\n\n        Iterator(size_t index, RangeIt it)\n            : m_index{index}, m_it{std::move(it)} {}\n\n        struct ValueType\n        {\n            size_t index;\n            decltype(*std::declval<RangeIt>()) element;\n        };\n\n        ValueType operator*() { return {m_index, *m_it}; }\n        Iterator& operator++() { ++m_index; ++m_it; return *this; }\n        Iterator operator++(int) { auto copy = *this; ++(*this); return copy; }\n\n        friend bool operator==(const Iterator& lhs, const Iterator& rhs)\n        {\n            return lhs.m_it == rhs.m_it;\n        }\n\n        const RangeIt& base() const { return m_it; }\n\n    private:\n        size_t m_index;\n        RangeIt m_it;\n    };\n\n    Iterator begin() const { return {0, std::begin(m_range)}; }\n    Iterator end()   const { return {(size_t)-1, std::end(m_range)}; }\n\n    Range m_range;\n};\n\nconstexpr auto enumerate()\n{\n    return ViewFactory{[](auto&& range) {\n        using Range = decltype(range);\n        return EnumerateView<DecayRange<Range>>{std::forward<Range>(range)};\n    }};\n}\n\ntemplate<typename Range, typename Transform>\nstruct TransformView\n{\n    using RangeIt = IteratorOf<Range>;\n    using ResType = decltype(std::declval<Transform>()(*std::declval<RangeIt>()));\n\n    struct Iterator\n    {\n        using iterator_category = typename std::iterator_traits<RangeIt>::iterator_category;\n        using value_type = std::remove_reference_t<ResType>;\n        using difference_type = typename std::iterator_traits<RangeIt>::difference_type;\n        using pointer = value_type*;\n        using reference = value_type&;\n\n        Iterator(Transform& transform, RangeIt it)\n            : m_it{std::move(it)}, m_transform{&transform} {}\n\n        decltype(auto) operator*() { return (*m_transform)(*m_it); }\n        decltype(auto) operator[](difference_type i) const { return (*m_transform)(m_it[i]); }\n\n        Iterator& operator++() { ++m_it; return *this; }\n        Iterator operator++(int) { auto copy = *this; ++m_it; return copy; }\n\n        Iterator& operator--() { --m_it; return *this; }\n        Iterator operator--(int) { auto copy = *this; --m_it; return copy; }\n\n        Iterator& operator+=(difference_type diff) { m_it += diff; return *this; }\n        Iterator& operator-=(difference_type diff) { m_it -= diff; return *this; }\n\n        Iterator operator+(difference_type diff) const { return {*m_transform, m_it + diff}; }\n        Iterator operator-(difference_type diff) const { return {*m_transform, m_it - diff}; }\n\n        friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.m_it == rhs.m_it; }\n        friend difference_type operator-(const Iterator& lhs, const Iterator& rhs) { return lhs.m_it - rhs.m_it; }\n\n        RangeIt base() const { return m_it; }\n\n    private:\n        RangeIt m_it;\n        Transform* m_transform;\n    };\n\n    Iterator begin() const { return {m_transform, std::begin(m_range)}; }\n    Iterator end()   const { return {m_transform, std::end(m_range)}; }\n\n    Range m_range;\n    mutable Transform m_transform;\n};\n\ntemplate<typename Transform>\nconstexpr auto transform(Transform t)\n{\n    return ViewFactory{[t = std::move(t)](auto&& range) {\n        using Range = decltype(range);\n        return TransformView<DecayRange<Range>, Transform>{std::forward<Range>(range), std::move(t)};\n    }};\n}\n\ntemplate<typename T, typename U>\nstruct is_pointer_like : std::false_type {};\n\ntemplate<typename T, typename U> requires std::is_same_v<std::remove_cvref_t<decltype(*std::declval<U>())>, std::remove_cvref_t<T>>\nstruct is_pointer_like<T, U> : std::true_type {};\n\ntemplate<typename M, typename T>\nconstexpr auto transform(M T::*member)\n{\n    return transform([member](auto&& arg) -> decltype(auto) {\n        using Arg = decltype(arg);\n        using Member = decltype(member);\n\n        auto get_object = [&] () mutable  -> decltype(auto) {\n            if constexpr (is_pointer_like<T, Arg>::value)\n                return *std::forward<Arg>(arg);\n            else\n                return std::forward<Arg>(arg);\n        };\n\n        if constexpr (std::is_member_function_pointer_v<Member>)\n            return (get_object().*member)();\n        else\n            return get_object().*member;\n    });\n}\n\ntemplate<typename Range, bool escape, bool include_separator,\n         typename Element = ValueOf<Range>,\n         typename ValueTypeParam = void>\nstruct SplitView\n{\n    using RangeIt = IteratorOf<Range>;\n    using ValueType = std::conditional_t<std::is_same<void, ValueTypeParam>::value,\n                                         std::pair<IteratorOf<Range>, IteratorOf<Range>>,\n                                         ValueTypeParam>;\n\n    struct Iterator\n    {\n        using difference_type = ptrdiff_t;\n        using value_type = ValueType;\n        using pointer = ValueType*;\n        using reference = ValueType&;\n        using iterator_category = std::forward_iterator_tag;\n\n        Iterator(RangeIt pos, const RangeIt& end, Element separator, Element escaper)\n         : done{pos == end}, pos{pos}, sep{pos}, end(end), separator{std::move(separator)}, escaper{std::move(escaper)}\n        {\n            bool escaped = false;\n            while (sep != end and (escaped or *sep != separator))\n            {\n                escaped = escape and not escaped and *sep == escaper;\n                ++sep;\n            }\n        }\n\n        Iterator& operator++() { advance(); return *this; }\n        Iterator operator++(int) { auto copy = *this; advance(); return copy; }\n\n        bool operator==(const Iterator& other) const { return pos == other.pos and done == other.done; }\n\n        ValueType operator*() { return {pos, (not include_separator or sep == end) ? sep : sep + 1}; }\n\n    private:\n        void advance()\n        {\n            if (sep == end)\n            {\n                pos = end;\n                done = true;\n                return;\n            }\n\n            pos = sep+1;\n            if (include_separator and pos == end)\n            {\n                done = true;\n                return;\n            }\n            bool escaped = escape and *sep == escaper;\n            for (sep = pos; sep != end; ++sep)\n            {\n                if (not escaped and *sep == separator)\n                    break;\n                escaped = escape and not escaped and *sep == escaper;\n            }\n        }\n\n        bool done;\n        RangeIt pos;\n        RangeIt sep;\n        RangeIt end;\n        Element separator;\n        Element escaper;\n    };\n\n    Iterator begin() const { return {std::begin(m_range), std::end(m_range), m_separator, m_escaper}; }\n    Iterator end()   const { return {std::end(m_range), std::end(m_range), m_separator, m_escaper}; }\n\n    Range m_range;\n    Element m_separator;\n    Element m_escaper;\n};\n\ntemplate<typename ValueType = void, typename Element>\nauto split(Element separator)\n{\n    return ViewFactory{[s = std::move(separator)](auto&& range) {\n        using Range = decltype(range);\n        return SplitView<DecayRange<Range>, false, false, Element, ValueType>{std::forward<Range>(range), std::move(s), {}};\n    }};\n}\n\ntemplate<typename ValueType = void, typename Element>\nauto split_after(Element separator)\n{\n    return ViewFactory{[s = std::move(separator)](auto&& range) {\n        using Range = decltype(range);\n        return SplitView<DecayRange<Range>, false, true, Element, ValueType>{std::forward<Range>(range), std::move(s), {}};\n    }};\n}\n\ntemplate<typename ValueType = void, typename Element>\nauto split(Element separator, Element escaper)\n{\n    return ViewFactory{[s = std::move(separator), e = std::move(escaper)](auto&& range) {\n        using Range = decltype(range);\n        return SplitView<DecayRange<Range>, true, false, Element, ValueType>{std::forward<Range>(range), std::move(s), std::move(e)};\n    }};\n}\n\ntemplate<typename Range>\nstruct FlattenedView\n{\n    using OuterIt = IteratorOf<Range>;\n    using InnerRange = ValueOf<Range>;\n    using InnerIt = IteratorOf<InnerRange>;\n\n    struct Iterator\n    {\n        using value_type = typename std::iterator_traits<InnerIt>::value_type;\n        using iterator_category = std::forward_iterator_tag;\n        using difference_type = std::size_t;\n        using reference = value_type&;\n        using pointer = value_type*;\n\n        Iterator() = default;\n        Iterator(OuterIt begin, OuterIt end) : m_outer_it{begin}, m_outer_end{end}\n        {\n            find_next_inner();\n        }\n\n        decltype(auto) operator*() {  return *m_inner_it; }\n\n        Iterator& operator++()\n        {\n            if (++m_inner_it == std::end(m_inner_range))\n            {\n                ++m_outer_it;\n                find_next_inner();\n            }\n            return *this;\n        }\n        Iterator operator++(int) { auto copy = *this; ++*this; return copy; }\n\n        friend bool operator==(const Iterator& lhs, const Iterator& rhs)\n        {\n            return lhs.m_outer_it == rhs.m_outer_it and lhs.m_inner_it == rhs.m_inner_it;\n        }\n\n        void find_next_inner()\n        {\n            m_inner_it = InnerIt{};\n            for (; m_outer_it != m_outer_end; ++m_outer_it)\n            {\n                m_inner_range = *m_outer_it;\n                if (std::begin(m_inner_range) != std::end(m_inner_range))\n                {\n                    m_inner_it = std::begin(m_inner_range);\n                    return;\n                }\n            }\n        }\n\n        OuterIt m_outer_it{};\n        OuterIt m_outer_end{};\n        InnerIt m_inner_it{};\n        RangeHolder<InnerRange> m_inner_range;\n    };\n\n    Iterator begin() const { return {std::begin(m_range), std::end(m_range)}; }\n    Iterator end()   const { return {std::end(m_range), std::end(m_range)}; }\n\n    Range m_range;\n};\n\nconstexpr auto flatten()\n{\n    return ViewFactory{[](auto&& range){\n        using Range = decltype(range);\n        return FlattenedView<DecayRange<Range>>{std::forward<Range>(range)};\n    }};\n}\n\ntemplate<typename Range1, typename Range2>\nstruct ConcatView\n{\n    using RangeIt1 = decltype(std::declval<Range1>().begin());\n    using RangeIt2 = decltype(std::declval<Range2>().begin());\n    using ValueType = typename std::common_type_t<typename std::iterator_traits<RangeIt1>::value_type,\n                                                  typename std::iterator_traits<RangeIt2>::value_type>;\n\n    struct Iterator\n    {\n        using difference_type = ptrdiff_t;\n        using value_type = ValueType;\n        using pointer = ValueType*;\n        using reference = ValueType&;\n        using iterator_category = std::forward_iterator_tag;\n\n        static_assert(std::is_convertible<typename std::iterator_traits<RangeIt1>::value_type, ValueType>::value, \"\");\n        static_assert(std::is_convertible<typename std::iterator_traits<RangeIt2>::value_type, ValueType>::value, \"\");\n\n        Iterator(RangeIt1 it1, RangeIt1 end1, RangeIt2 it2)\n            : m_it1(std::move(it1)), m_end1(std::move(end1)),\n              m_it2(std::move(it2)) {}\n\n        decltype(auto) operator*() { return is2() ? *m_it2 : *m_it1; }\n        Iterator& operator++() { if (is2()) ++m_it2; else ++m_it1; return *this; }\n        Iterator operator++(int) { auto copy = *this; ++*this; return copy; }\n\n        friend bool operator==(const Iterator& lhs, const Iterator& rhs) = default;\n\n    private:\n        bool is2() const { return m_it1 == m_end1; }\n\n        RangeIt1 m_it1;\n        RangeIt1 m_end1;\n        RangeIt2 m_it2;\n    };\n\n    Iterator begin() const { return {m_range1.begin(), m_range1.end(), m_range2.begin()}; }\n    Iterator end()   const { return {m_range1.end(), m_range1.end(), m_range2.end()}; }\n\n    Range1 m_range1;\n    Range2 m_range2;\n};\n\ntemplate<typename Range1, typename Range2>\nConcatView<DecayRange<Range1>, DecayRange<Range2>> concatenated(Range1&& range1, Range2&& range2)\n{\n    return {range1, range2};\n}\n\ntemplate<typename Range, typename T>\nauto find(Range&& range, const T& value)\n{\n    using std::begin; using std::end;\n    return std::find(begin(range), end(range), value);\n}\n\ntemplate<typename Range, typename T>\nauto find_if(Range&& range, T&& op)\n{\n    using std::begin; using std::end;\n    return std::find_if(begin(range), end(range), std::forward<T>(op));\n}\n\ntemplate<typename Range, typename T>\nbool contains(Range&& range, const T& value)\n{\n    using std::end;\n    return find(range, value) != end(range);\n}\n\ntemplate<typename Range, typename T>\nbool all_of(Range&& range, T&& op)\n{\n    using std::begin; using std::end;\n    return std::all_of(begin(range), end(range), std::forward<T>(op));\n}\n\ntemplate<typename Range, typename T>\nbool any_of(Range&& range, T&& op)\n{\n    using std::begin; using std::end;\n    return std::any_of(begin(range), end(range), std::forward<T>(op));\n}\n\ntemplate<typename Range, typename T>\nauto remove_if(Range&& range, T&& op)\n{\n    using std::begin; using std::end;\n    return std::remove_if(begin(range), end(range), std::forward<T>(op));\n}\n\ntemplate<typename Range, typename U>\nvoid unordered_erase(Range&& vec, U&& value)\n{\n    auto it = find(vec, std::forward<U>(value));\n    if (it != vec.end())\n    {\n        using std::swap;\n        swap(vec.back(), *it);\n        vec.pop_back();\n    }\n}\n\ntemplate<typename Range, typename Init, typename BinOp>\nInit accumulate(Range&& c, Init&& init, BinOp&& op)\n{\n    using std::begin; using std::end;\n    return std::accumulate(begin(c), end(c), init, std::forward<BinOp>(op));\n}\n\ntemplate<typename Range, typename Compare, typename Func>\nvoid for_n_best(Range&& c, size_t count, Compare&& compare, Func&& func)\n{\n    using std::begin; using std::end;\n    auto b = begin(c), e = end(c);\n    std::make_heap(b, e, compare);\n    while (count > 0 and b != e)\n    {\n        if (func(*b))\n            --count;\n        std::pop_heap(b, e--, compare);\n    }\n}\n\ntemplate<typename Container>\nauto gather()\n{\n    return ViewFactory{[](auto&& range) {\n        using std::begin; using std::end;\n        return Container(begin(range), end(range));\n    }};\n}\n\ntemplate<template <typename Element> class Container>\nauto gather()\n{\n    return ViewFactory{[](auto&& range) {\n        using std::begin; using std::end;\n        using ValueType = std::remove_cv_t<std::remove_reference_t<decltype(*begin(range))>>;\n        return Container<ValueType>(begin(range), end(range));\n    }};\n}\n\ntemplate<typename ExceptionType, size_t size, bool exact_size = true>\nauto static_gather()\n{\n    return []<size_t... Indexes>(std::index_sequence<Indexes...>) {\n        return ViewFactory{[] (auto&& range) {\n            using std::begin; using std::end;\n            auto it = begin(range), end_it = end(range);\n            size_t i = 0;\n            auto elem = [&](size_t index) {\n                for (; i < index; ++i)\n                    if (++it == end_it) throw ExceptionType{i};\n                return *it;\n            };\n            // Note that initializer lists elements are guaranteed to be sequenced\n            Array<std::remove_cvref_t<decltype(*begin(range))>, sizeof...(Indexes)> res{{elem(Indexes)...}};\n            if (exact_size and ++it != end_it)\n                throw ExceptionType{++i};\n            return res;\n        }};\n    }(std::make_index_sequence<size>());\n}\n\n}\n\n#endif // ranges_hh_INCLUDED\n"
  },
  {
    "path": "src/ranked_match.cc",
    "content": "#include \"ranked_match.hh\"\n\n#include \"flags.hh\"\n#include \"unit_tests.hh\"\n#include \"utf8_iterator.hh\"\n#include \"optional.hh\"\n#include \"ranges.hh\"\n#include \"word_splitter.hh\"\n\n#include <algorithm>\n\nnamespace Kakoune\n{\n\nUsedLetters used_letters(StringView str)\n{\n    UsedLetters res = 0;\n    for (auto c : str)\n    {\n        if (c >= 'a' and c <= 'z')\n            res |= 1uLL << (c - 'a');\n        else if (c >= 'A' and c <= 'Z')\n            res |= 1uLL << (c - 'A' + 26);\n        else if (c == '_')\n            res |= 1uLL << 53;\n        else if (c == '-')\n            res |= 1uLL << 54;\n        else\n            res |= 1uLL << 63;\n    }\n    return res;\n}\n\nbool matches(UsedLetters query, UsedLetters letters)\n{\n    return (query & letters) == query;\n}\n\nusing Utf8It = utf8::iterator<const char*>;\n\nstatic int count_word_boundaries_match(StringView candidate, StringView query)\n{\n    int count = 0;\n    Utf8It query_it{query.begin(), query};\n    Codepoint prev = 0;\n    for (Utf8It it{candidate.begin(), candidate}; it != candidate.end(); ++it)\n    {\n        const Codepoint c = *it;\n        const bool is_word_boundary = prev == 0 or\n                                      (!is_word(prev, {}) and is_word(c, {})) or\n                                      (is_lower(prev) and is_upper(c));\n        prev = c;\n\n        if (not is_word_boundary)\n            continue;\n\n        const Codepoint lc = to_lower(c);\n        for (auto qit = query_it; qit != query.end(); ++qit)\n        {\n            const Codepoint qc = *qit;\n            if (qc == (is_lower(qc) ? lc  : c))\n            {\n                ++count;\n                query_it = qit+1;\n                break;\n            }\n        }\n        if (query_it == query.end())\n            break;\n    }\n    return count;\n}\n\nstatic int count_full_word_match(StringView candidate, StringView query)\n{\n    int count = 0;\n    WordSplitter query_words{query, {}};\n    WordSplitter candidate_words{candidate, {}};\n    for (auto query_word : query_words)\n    {\n        for (auto word : candidate_words)\n        {\n            if (word == query_word)\n            {\n                ++count;\n                break;\n            }\n        }\n    }\n    return count;\n}\n\nstatic bool smartcase_eq(Codepoint candidate, Codepoint query)\n{\n    return query == (is_lower(query) ? to_lower(candidate) : candidate);\n}\n\nstruct SubseqRes\n{\n    int max_index;\n    bool single_word;\n};\n\nstatic Optional<SubseqRes> subsequence_match_smart_case(StringView str, StringView subseq)\n{\n    bool single_word = true;\n    int max_index = -1;\n    auto it = str.begin();\n    int index = 0;\n    for (auto subseq_it = subseq.begin(); subseq_it != subseq.end();)\n    {\n        if (it == str.end())\n            return {};\n        const Codepoint c = utf8::read_codepoint(subseq_it, subseq.end());\n        if (single_word and not is_word(c))\n            single_word = false;\n        while (true)\n        {\n            auto str_c = utf8::read_codepoint(it, str.end());\n            if (smartcase_eq(str_c, c))\n                break;\n\n            if (max_index != -1 and single_word and not is_word(str_c))\n                single_word = false;\n\n            ++index;\n            if (it == str.end())\n                return {};\n        }\n        max_index = index++;\n    }\n    return SubseqRes{max_index, single_word};\n}\n\ntemplate<typename TestFunc>\nRankedMatch::RankedMatch(StringView candidate, StringView query, TestFunc func)\n{\n    if (query.length() > candidate.length())\n        return;\n\n    if (query.empty())\n    {\n        m_candidate = candidate;\n        m_matches = true;\n        return;\n    }\n\n    if (not func())\n        return;\n\n    auto res = subsequence_match_smart_case(candidate, query);\n    if (not res)\n        return;\n\n    m_candidate = candidate;\n    m_matches = true;\n    m_max_index = res->max_index;\n\n    if (res->single_word)\n        m_flags |= Flags::SingleWord;\n\n    if (auto it = find(candidate | reverse() | skip(1), '/').base();\n        it == candidate.begin() or subsequence_match_smart_case({it, candidate.end()}, query))\n    {\n        m_flags |= Flags::BaseName;\n        if ((candidate.end() - it) >= query.length() and\n            std::equal(Utf8It{query.begin(), query}, Utf8It{query.end(), query}, Utf8It{it, candidate},\n                       [](Codepoint query, Codepoint candidate) { return smartcase_eq(candidate, query); }))\n            m_flags |= Flags::Prefix;\n    }\n\n    auto it = std::search(candidate.begin(), candidate.end(),\n                          query.begin(), query.end(), smartcase_eq);\n    if (it != candidate.end())\n    {\n        m_flags |= Flags::Contiguous;\n        if (std::all_of(Utf8It{query.begin(), query}, Utf8It{query.end(), query}, [](auto cp) { return is_word(cp); }))\n            m_flags |= Flags::SingleWord;\n        if (it == candidate.begin())\n        {\n            m_flags |= Flags::Prefix;\n            if (query.length() == candidate.length())\n            {\n                m_flags |= Flags::SmartFullMatch;\n                if (candidate == query)\n                    m_flags |= Flags::FullMatch;\n            }\n        }\n    }\n\n    m_full_word_match_count = count_full_word_match(candidate, query);\n    m_word_boundary_match_count = count_word_boundaries_match(candidate, query);\n    if (m_word_boundary_match_count == query.length())\n        m_flags |= Flags::OnlyWordBoundary;\n}\n\nRankedMatch::RankedMatch(StringView candidate, UsedLetters candidate_letters,\n                         StringView query, UsedLetters query_letters)\n    : RankedMatch{candidate, query, [&] {\n        return matches(to_lower(query_letters), to_lower(candidate_letters)) and\n               matches(query_letters & upper_mask, candidate_letters & upper_mask);\n    }} {}\n\n\nRankedMatch::RankedMatch(StringView candidate, StringView query)\n    : RankedMatch{candidate, query, [] { return true; }}\n{\n}\n\nstatic bool is_word_boundary(Codepoint prev, Codepoint c)\n{\n    return (is_word(prev, {})) != is_word(c, {}) or\n           (is_lower(prev) != is_lower(c));\n}\n\nbool RankedMatch::operator<(const RankedMatch& other) const\n{\n    kak_assert((bool)*this and (bool)other);\n\n    const auto diff = m_flags ^ other.m_flags;\n    // flags are different, use their ordering to return the first match\n    if (diff != Flags::None)\n        return (int)(m_flags & diff) > (int)(other.m_flags & diff);\n\n    // If we are SingleWord, we dont want to take word boundaries from other\n    // words into account.\n    if (not (m_flags & (Flags::Prefix | Flags::SingleWord)) and\n        m_word_boundary_match_count != other.m_word_boundary_match_count)\n        return m_word_boundary_match_count > other.m_word_boundary_match_count;\n\n    if (m_full_word_match_count != other.m_full_word_match_count)\n        return m_full_word_match_count > other.m_full_word_match_count;\n\n    if (m_max_index != other.m_max_index)\n        return m_max_index < other.m_max_index;\n\n    if (m_input_sequence_number != other.m_input_sequence_number)\n        return m_input_sequence_number < other.m_input_sequence_number;\n\n    // Reorder codepoints to improve matching behaviour\n    auto order = [](Codepoint cp) { return cp == '/' ? 0 : cp; };\n\n    auto it1 = m_candidate.begin(), it2 = other.m_candidate.begin();\n    const auto begin1 = it1, begin2 = it2;\n    const auto end1 = m_candidate.end(), end2 = other.m_candidate.end();\n    auto last1 = it1, last2 = it2;\n    while (true)\n    {\n        // find next mismatch\n        while (it1 != end1 and it2 != end2 and *it1 == *it2)\n            ++it1, ++it2;\n\n        if (it1 == end1 or it2 == end2)\n            return it1 == end1 and it2 != end2;\n\n        // compare codepoints\n        it1 = utf8::character_start(it1, last1);\n        it2 = utf8::character_start(it2, last2);\n        const auto itsave1 = it1, itsave2 = it2;\n        const auto cp1 = utf8::read_codepoint(it1, end1);\n        const auto cp2 = utf8::read_codepoint(it2, end2);\n        if (cp1 != cp2)\n        {\n            const auto cplast1 = utf8::prev_codepoint(itsave1, begin1);\n            const auto cplast2 = utf8::prev_codepoint(itsave2, begin2);\n            const bool is_wb1 = is_word_boundary(cplast1, cp1);\n            const bool is_wb2 = is_word_boundary(cplast2, cp2);\n            if (is_wb1 != is_wb2)\n                return is_wb1;\n\n            const bool low1 = is_lower(cp1);\n            const bool low2 = is_lower(cp2);\n            if (low1 != low2)\n                return low1;\n\n            return order(cp1) < order(cp2);\n        }\n        last1 = it1; last2 = it2;\n    }\n}\n\nUnitTest test_ranked_match{[] {\n    auto preferred = [](StringView query, StringView better, StringView worse) {\n        return RankedMatch{better, query} < RankedMatch{worse, query};\n    };\n\n    kak_assert(count_word_boundaries_match(\"run_all_tests\", \"rat\") == 3);\n    kak_assert(count_word_boundaries_match(\"run_all_tests\", \"at\") == 2);\n    kak_assert(count_word_boundaries_match(\"countWordBoundariesMatch\", \"wm\") == 2);\n    kak_assert(count_word_boundaries_match(\"countWordBoundariesMatch\", \"cobm\") == 3);\n    kak_assert(count_word_boundaries_match(\"countWordBoundariesMatch\", \"cWBM\") == 4);\n    kak_assert(preferred(\"so\", \"source\", \"source_data\"));\n    kak_assert(not preferred(\"so\", \"source_data\", \"source\"));\n    kak_assert(not preferred(\"so\", \"source\", \"source\"));\n    kak_assert(preferred(\"wo\", \"single/word\", \"multiw/ord\"));\n    kak_assert(preferred(\"foobar\", \"foo/bar/foobar\", \"foo/bar/baz\"));\n    kak_assert(preferred(\"db\", \"delete-buffer\", \"debug\"));\n    kak_assert(preferred(\"ct\", \"create_task\", \"constructor\"));\n    kak_assert(preferred(\"cla\", \"class\", \"class::attr\"));\n    kak_assert(preferred(\"meta\", \"meta/\", \"meta-a/\"));\n    kak_assert(preferred(\"find\", \"find(1p)\", \"findfs(8)\"));\n    kak_assert(preferred(\"fin\", \"find(1p)\", \"findfs(8)\"));\n    kak_assert(preferred(\"sys_find\", \"sys_find(1p)\", \"sys_findfs(8)\"));\n    kak_assert(preferred(\"\", \"init\", \"__init__\"));\n    kak_assert(preferred(\"ini\", \"init\", \"__init__\"));\n    kak_assert(preferred(\"\", \"a\", \"b\"));\n    kak_assert(preferred(\"expresins\", \"expresions\", \"expressionism's\"));\n    kak_assert(preferred(\"foo_b\", \"foo/bar/foo_bar.baz\", \"test/test_foo_bar.baz\"));\n    kak_assert(preferred(\"foo_b\", \"bar/bar_qux/foo_bar.baz\", \"foo/test_foo_bar.baz\"));\n    kak_assert(preferred(\"foo_bar\", \"bar/foo_bar.baz\", \"foo_bar/qux.baz\"));\n    kak_assert(preferred(\"fb\", \"foo_bar/\", \"foo.bar\"));\n    kak_assert(preferred(\"foo_bar\", \"test_foo_bar\", \"foo_test_bar\"));\n    kak_assert(preferred(\"rm.cc\", \"src/ranked_match.cc\", \"test/README.asciidoc\"));\n    kak_assert(preferred(\"luaremote\", \"src/script/LuaRemote.cpp\", \"tests/TestLuaRemote.cpp\"));\n    kak_assert(preferred(\"lang/haystack/needle.c\", \"git.evilcorp.com/language/haystack/aaa/needle.c\", \"git.evilcorp.com/aaa/ng/wrong-haystack/needle.cpp\"));\n    kak_assert(preferred(\"evilcorp-lint/bar.go\", \"scripts/evilcorp-lint/foo/bar.go\", \"src/evilcorp-client/foo/bar.go\"));\n}};\n\nUnitTest test_used_letters{[]()\n{\n    kak_assert(used_letters(\"abcd\") == to_lower(used_letters(\"abcdABCD\")));\n}};\n\n}\n"
  },
  {
    "path": "src/ranked_match.hh",
    "content": "#ifndef ranked_match_hh_INCLUDED\n#define ranked_match_hh_INCLUDED\n\n#include \"string.hh\"\n#include \"meta.hh\"\n\n#include <cstdint>\n\nnamespace Kakoune\n{\n\nusing UsedLetters = uint64_t;\nUsedLetters used_letters(StringView str);\n\nconstexpr UsedLetters upper_mask = 0xFFFFFFC000000;\n\ninline UsedLetters to_lower(UsedLetters letters)\n{\n    return ((letters & upper_mask) >> 26) | (letters & (~upper_mask));\n}\n\nstruct RankedMatch\n{\n    RankedMatch(StringView candidate, StringView query);\n    RankedMatch(StringView candidate, UsedLetters candidate_letters,\n                StringView query, UsedLetters query_letters);\n\n    const StringView& candidate() const { return m_candidate; }\n    bool operator<(const RankedMatch& other) const;\n    bool operator==(const RankedMatch& other) const { return m_candidate == other.m_candidate; }\n\n    explicit operator bool() const { return m_matches; }\n\n    void set_input_sequence_number(size_t i) { m_input_sequence_number = i; }\n\nprivate:\n    template<typename TestFunc>\n    RankedMatch(StringView candidate, StringView query, TestFunc test);\n\n    enum class Flags : int\n    {\n        None = 0,\n        // Order is important, the highest bit has precedence for comparison\n        SingleWord       = 1 << 0,\n        Contiguous       = 1 << 1,\n        OnlyWordBoundary = 1 << 2,\n        Prefix           = 1 << 3,\n        BaseName         = 1 << 4,\n        SmartFullMatch   = 1 << 5,\n        FullMatch        = 1 << 6,\n    };\n    friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; }\n\n    StringView m_candidate{};\n    bool m_matches = false;\n    Flags m_flags = Flags::None;\n    int m_full_word_match_count = 0;\n    int m_word_boundary_match_count = 0;\n    int m_max_index = 0;\n    size_t m_input_sequence_number = 0;\n};\n\n}\n\n#endif // ranked_match_hh_INCLUDED\n"
  },
  {
    "path": "src/ref_ptr.hh",
    "content": "#ifndef ref_ptr_hh_INCLUDED\n#define ref_ptr_hh_INCLUDED\n\nnamespace Kakoune\n{\n\nstruct RefCountable\n{\n    RefCountable() = default;\n    RefCountable(const RefCountable&) {}\n    RefCountable(RefCountable&&) {}\n    virtual ~RefCountable() = default;\n\n    RefCountable& operator=(const RefCountable&) { return *this; }\n    RefCountable& operator=(RefCountable&&) { return *this; }\n\n    int refcount = 0;\n};\n\nstruct RefCountablePolicy\n{\n    static void inc_ref(RefCountable* r, void*) noexcept { ++r->refcount; }\n    static void dec_ref(RefCountable* r, void*) noexcept { if (--r->refcount == 0) delete r; }\n    static void ptr_moved(RefCountable*, void*, void*) noexcept {}\n};\n\ntemplate<typename T, typename Policy = RefCountablePolicy>\nstruct RefPtr\n{\n    RefPtr() = default;\n    explicit RefPtr(T* ptr) : m_ptr(ptr) { acquire(); }\n    ~RefPtr() noexcept { release(); }\n    RefPtr(const RefPtr& other) : m_ptr(other.m_ptr) { acquire(); }\n    RefPtr(RefPtr&& other)\n        noexcept(noexcept(moved(nullptr)))\n        : m_ptr(other.m_ptr) { other.m_ptr = nullptr; moved(&other); }\n\n    RefPtr& operator=(const RefPtr& other)\n    {\n        if (other.m_ptr != m_ptr)\n        {\n            release();\n            m_ptr = other.m_ptr;\n            acquire();\n        }\n        return *this;\n    }\n\n    RefPtr& operator=(RefPtr&& other)\n    {\n        release();\n        m_ptr = other.m_ptr;\n        other.m_ptr = nullptr;\n        moved(&other);\n        return *this;\n    }\n\n    RefPtr& operator=(T* ptr)\n    {\n        if (ptr != m_ptr)\n        {\n            release();\n            m_ptr = ptr;\n            acquire();\n        }\n        return *this;\n    }\n\n    [[gnu::always_inline]]\n    T* operator->() const { return m_ptr; }\n    [[gnu::always_inline]]\n    T& operator*() const { return *m_ptr; }\n\n    [[gnu::always_inline]]\n    T* get() const { return m_ptr; }\n\n    [[gnu::always_inline]]\n    explicit operator bool() const { return m_ptr; }\n\n    void reset(T* ptr = nullptr)\n    {\n        if (ptr == m_ptr)\n            return;\n        release();\n        m_ptr = ptr;\n        acquire();\n    }\n\n    friend bool operator==(const RefPtr& lhs, const RefPtr& rhs) = default;\n    friend bool operator==(const RefPtr& lhs, const T* rhs) { return lhs.m_ptr == rhs; }\n\nprivate:\n    T* m_ptr = nullptr;\n\n    [[gnu::always_inline]]\n    void acquire()\n    {\n        if (m_ptr)\n            Policy::inc_ref(m_ptr, this);\n    }\n\n    [[gnu::always_inline]]\n    void release() noexcept\n    {\n        if (m_ptr)\n            Policy::dec_ref(m_ptr, this);\n    }\n\n    [[gnu::always_inline]]\n    void moved(void* from)\n        noexcept(noexcept(Policy::ptr_moved(nullptr, nullptr, nullptr)))\n    {\n        if (m_ptr)\n            Policy::ptr_moved(m_ptr, from, this);\n    }\n};\n\n}\n\n#endif // ref_ptr_hh_INCLUDED\n"
  },
  {
    "path": "src/regex.cc",
    "content": "#include \"regex.hh\"\n#include \"string_utils.hh\"\n\nnamespace Kakoune\n{\n\nRegex::Regex(StringView re, RegexCompileFlags flags)\n    : m_impl{new Impl{}},\n      m_str{re.str()}\n{\n    static_cast<CompiledRegex&>(*m_impl) = compile_regex(re, flags);\n}\n\nint Regex::named_capture_index(StringView name) const\n{\n    for (auto capture : m_impl->named_captures)\n    {\n        if (capture.name == name)\n            return capture.index;\n    }\n    return -1;\n}\n\nString option_to_string(const Regex& re, Quoting quoting)\n{\n    return option_to_string(re.str(), quoting);\n}\n\nRegex option_from_string(Meta::Type<Regex>, StringView str)\n{\n    return Regex{str};\n}\n\n}\n"
  },
  {
    "path": "src/regex.hh",
    "content": "#ifndef regex_hh_INCLUDED\n#define regex_hh_INCLUDED\n\n#include \"string.hh\"\n#include \"regex_vm.hh\"\n#include \"ref_ptr.hh\"\n\nnamespace Kakoune\n{\n\n// Regex that keeps track of its string representation\nclass Regex\n{\npublic:\n    Regex() = default;\n\n    explicit Regex(StringView re, RegexCompileFlags flags = RegexCompileFlags::None);\n    bool empty() const { return m_str.empty(); }\n    bool operator==(const Regex& other) const { return m_str == other.m_str; }\n\n    const String& str() const { return m_str; }\n\n    size_t mark_count() const { return m_impl->save_count / 2 - 1; }\n    int named_capture_index(StringView name) const;\n\n    static constexpr const char* option_type_name = \"regex\";\n\n    const CompiledRegex* impl() const { return m_impl.get(); }\n\nprivate:\n    struct Impl : RefCountable, CompiledRegex {};\n\n    RefPtr<Impl> m_impl;\n    String m_str;\n};\n\ntemplate<typename Iterator>\nstruct MatchResults\n{\n    struct SubMatch : std::pair<Iterator, Iterator>\n    {\n        SubMatch() = default;\n        SubMatch(Iterator begin, Iterator end)\n            : std::pair<Iterator, Iterator>{begin, end}, matched{static_cast<bool>(begin)}\n        {}\n\n        bool matched = false;\n    };\n\n    struct iterator\n    {\n        using difference_type = size_t;\n        using value_type = SubMatch;\n        using pointer = SubMatch*;\n        using reference = SubMatch;\n        using iterator_category = std::bidirectional_iterator_tag;\n        using It = typename Vector<Iterator, MemoryDomain::Regex>::const_iterator;\n\n        iterator() = default;\n        iterator(It it) : m_it{std::move(it)} {}\n\n        iterator& operator--() { m_it += 2; return *this; }\n        iterator& operator++() { m_it += 2; return *this; }\n        SubMatch operator*() const { return {*m_it, *(m_it+1)}; }\n\n        friend bool operator==(const iterator& lhs, const iterator& rhs) = default;\n    private:\n\n        It m_it;\n    };\n\n    MatchResults() = default;\n    MatchResults(Vector<Iterator, MemoryDomain::Regex> values) : m_values{std::move(values)} {}\n\n    iterator begin() const { return iterator{m_values.begin()}; }\n    iterator cbegin() const { return iterator{m_values.cbegin()}; }\n    iterator end() const { return iterator{m_values.end()}; }\n    iterator cend() const { return iterator{m_values.cend()}; }\n\n    size_t size() const { return m_values.size() / 2; }\n    bool empty() const { return m_values.empty(); }\n\n    SubMatch operator[](size_t i) const\n    {\n        return i * 2 < m_values.size() ?\n            SubMatch{m_values[i*2], m_values[i*2+1]} : SubMatch{};\n    }\n\n    friend bool operator==(const MatchResults& lhs, const MatchResults& rhs) = default;\n\n    void swap(MatchResults& other)\n    {\n        m_values.swap(other.m_values);\n    }\n\n    Vector<Iterator, MemoryDomain::Regex>& values() { return m_values; }\n\nprivate:\n    Vector<Iterator, MemoryDomain::Regex> m_values;\n};\n\ninline RegexExecFlags match_flags(bool bol, bool eol, bool bow, bool eow)\n{\n    return (bol ? RegexExecFlags::None : RegexExecFlags::NotBeginOfLine) |\n           (eol ? RegexExecFlags::None : RegexExecFlags::NotEndOfLine) |\n           (bow ? RegexExecFlags::None : RegexExecFlags::NotBeginOfWord) |\n           (eow ? RegexExecFlags::None : RegexExecFlags::NotEndOfWord);\n}\n\nstruct NoopIdle\n{\n    void operator()() {}\n};\n\ntemplate<typename It, typename IdleFunc = NoopIdle>\nbool regex_match(It begin, It end, const Regex& re, IdleFunc&& idle_func = {})\n{\n    ThreadedRegexVM<It, RegexMode::Forward | RegexMode::AnyMatch | RegexMode::NoSaves> vm{*re.impl()};\n    return vm.exec(begin, end, begin, end, RegexExecFlags::None, idle_func);\n}\n\ntemplate<typename It, typename IdleFunc = NoopIdle>\nbool regex_match(It begin, It end, MatchResults<It>& res, const Regex& re, IdleFunc&& idle_func = {})\n{\n    res.values().clear();\n    ThreadedRegexVM<It, RegexMode::Forward> vm{*re.impl()};\n    if (vm.exec(begin, end, begin, end, RegexExecFlags::None, idle_func))\n    {\n        std::copy(vm.captures().begin(), vm.captures().end(), std::back_inserter(res.values()));\n        return true;\n    }\n    return false;\n}\n\ntemplate<typename It, typename IdleFunc = NoopIdle>\nbool regex_search(It begin, It end, It subject_begin, It subject_end, const Regex& re,\n                  RegexExecFlags flags = RegexExecFlags::None, IdleFunc&& idle_func = {})\n{\n    ThreadedRegexVM<It, RegexMode::Forward | RegexMode::Search | RegexMode::AnyMatch | RegexMode::NoSaves> vm{*re.impl()};\n    return vm.exec(begin, end, subject_begin, subject_end, flags, idle_func);\n}\n\ntemplate<typename It, RegexMode mode = RegexMode::Forward, typename IdleFunc = NoopIdle>\nbool regex_search(It begin, It end, It subject_begin, It subject_end,\n                  MatchResults<It>& res, const Regex& re,\n                  RegexExecFlags flags = RegexExecFlags::None,\n                  IdleFunc&& idle_func = {})\n{\n    res.values().clear();\n    ThreadedRegexVM<It, mode | RegexMode::Search> vm{*re.impl()};\n    if (vm.exec(begin, end, subject_begin, subject_end, flags, idle_func))\n    {\n        std::move(vm.captures().begin(), vm.captures().end(), std::back_inserter(res.values()));\n        return true;\n    }\n    return false;\n}\n\ntemplate<typename It, typename IdleFunc = NoopIdle>\nbool backward_regex_search(It begin, It end, It subject_begin, It subject_end,\n                           MatchResults<It>& res, const Regex& re,\n                           RegexExecFlags flags = RegexExecFlags::None,\n                           IdleFunc&& idle_func = {})\n{\n    return regex_search<It, RegexMode::Backward>(begin, end, subject_begin, subject_end, res, re, flags, idle_func);\n}\n\nenum class Quoting;\nString option_to_string(const Regex& re, Quoting quoting);\nRegex option_from_string(Meta::Type<Regex>, StringView str);\n\ntemplate<typename Iterator, RegexMode mode = RegexMode::Forward,\n         typename VmArg = const Regex, typename IdleFunc = NoopIdle>\nstruct RegexIterator\n{\n    static_assert(has_direction(mode));\n    static constexpr bool forward = mode & RegexMode::Forward;\n    using ValueType = MatchResults<Iterator>;\n    struct Sentinel{};\n    struct It\n    {\n        It(RegexIterator& base) : m_base(base), m_valid{m_base.next()} {}\n\n        const ValueType& operator*() const { kak_assert(m_valid); return m_base.m_results; }\n        const ValueType* operator->() const { kak_assert(m_valid); return &m_base.m_results; }\n\n        It& operator++() { m_valid = m_base.next(); return *this; }\n        bool operator==(Sentinel) const { return not m_valid; }\n\n        RegexIterator& m_base;\n        bool m_valid;\n    };\n\n    RegexIterator(Iterator begin, Iterator end,\n                  Iterator subject_begin, Iterator subject_end,\n                  VmArg& vm_arg, RegexExecFlags flags = RegexExecFlags::None,\n                  IdleFunc idle_func = {})\n        : m_vm{make_vm(vm_arg)}, m_next_pos{forward ? begin : end},\n          m_begin{std::move(begin)}, m_end{std::move(end)},\n          m_subject_begin{std::move(subject_begin)}, m_subject_end{std::move(subject_end)},\n          m_flags{flags}, m_idle_func{idle_func} {}\n\n    RegexIterator(const Iterator& begin, const Iterator& end,\n                  VmArg& vm_arg, RegexExecFlags flags = RegexExecFlags::None,\n                  IdleFunc idle_func = {})\n        : RegexIterator{begin, end, begin, end, vm_arg, flags, idle_func} {}\n\n    It begin() { return {*this}; }\n    Sentinel end() const { return {}; }\n\nprivate:\n    bool next()\n    {\n        auto additional_flags = RegexExecFlags::None;\n        if (m_results.size() and m_results[0].first == m_results[0].second)\n            additional_flags |= RegexExecFlags::NotInitialNull;\n\n        if (not m_vm.exec(forward ? m_next_pos : m_begin, forward ? m_end : m_next_pos,\n                          m_subject_begin, m_subject_end, m_flags | additional_flags,\n                          m_idle_func))\n            return false;\n\n        m_results.values().clear();\n        std::move(m_vm.captures().begin(), m_vm.captures().end(), std::back_inserter(m_results.values()));\n        m_next_pos = forward ? m_results[0].second : m_results[0].first;\n        kak_assert(forward ? (m_next_pos <= m_end) : (m_next_pos >= m_begin));\n        return true;\n    }\n\n    using RegexVM = ThreadedRegexVM<Iterator, mode | RegexMode::Search>;\n    static RegexVM& make_vm(RegexVM& vm) { return vm; }\n    static RegexVM make_vm(const Regex& regex) { return {*regex.impl()}; }\n\n    decltype(make_vm(std::declval<VmArg&>())) m_vm;\n    MatchResults<Iterator> m_results;\n    Iterator m_next_pos{};\n    const Iterator m_begin{};\n    const Iterator m_end{};\n    const Iterator m_subject_begin{};\n    const Iterator m_subject_end{};\n    const RegexExecFlags m_flags = RegexExecFlags::None;\n    IdleFunc m_idle_func;\n};\n\n}\n\n#endif // regex_hh_INCLUDED\n"
  },
  {
    "path": "src/regex_vm.cc",
    "content": "#include \"regex_vm.hh\"\n\n#include \"string.hh\"\n#include \"unicode.hh\"\n#include \"unit_tests.hh\"\n#include \"utf8.hh\"\n#include \"utf8_iterator.hh\"\n#include \"format.hh\"\n#include \"optional.hh\"\n#include \"vector.hh\"\n#include \"utils.hh\"\n#include \"ranges.hh\"\n\n#include <cstdio>\n#include <cstring>\n#include <limits>\n\nnamespace Kakoune\n{\n\nconstexpr Codepoint CompiledRegex::StartDesc::count;\n\nnamespace\n{\n\nstruct ParsedRegex\n{\n    enum Op : char\n    {\n        Literal,\n        AnyChar,\n        AnyCharExceptNewLine,\n        CharClass,\n        CharType,\n        Sequence,\n        Alternation,\n        LineStart,\n        LineEnd,\n        WordBoundary,\n        NotWordBoundary,\n        SubjectBegin,\n        SubjectEnd,\n        ResetStart,\n        LookAhead,\n        NegativeLookAhead,\n        LookBehind,\n        NegativeLookBehind,\n    };\n\n    struct Quantifier\n    {\n        static constexpr int16_t infinite = std::numeric_limits<int16_t>::max();\n\n        int16_t min = 0, max = 0;\n        bool greedy = true;\n\n        bool allows_none() const { return min == 0; }\n        bool allows_infinite_repeat() const { return max == infinite; };\n\n        friend bool operator==(Quantifier, Quantifier) = default;\n    };\n\n    using NodeIndex = int16_t;\n    struct Node\n    {\n        Op op;\n        bool ignore_case;\n        NodeIndex children_end;\n        Codepoint value;\n        Quantifier quantifier;\n    };\n\n    Vector<Node, MemoryDomain::Regex> nodes;\n\n    Vector<CharacterClass, MemoryDomain::Regex> character_classes;\n    Vector<CompiledRegex::NamedCapture, MemoryDomain::Regex> named_captures;\n    uint32_t capture_count;\n};\n\ntemplate<RegexMode mode = RegexMode::Forward>\nstruct Children\n{\n    static_assert(has_direction(mode));\n    using Index = ParsedRegex::NodeIndex;\n    struct Sentinel {};\n    struct Iterator\n    {\n        static constexpr bool forward = mode & RegexMode::Forward;\n        Iterator(ArrayView<const ParsedRegex::Node> nodes, Index index)\n          : m_nodes{nodes},\n            m_pos(forward ? index+1 : find_prev(index, nodes[index].children_end)),\n            m_end(forward ? nodes[index].children_end : index)\n        {}\n\n        Iterator& operator++()\n        {\n            m_pos = forward ? m_nodes[m_pos].children_end : find_prev(m_end, m_pos);\n            return *this;\n        }\n\n        Index operator*() const { return m_pos; }\n        bool operator!=(Sentinel) const { return m_pos != m_end; }\n\n    private:\n        Index find_prev(Index parent, Index pos) const\n        {\n            Index child = parent+1;\n            if (child == pos)\n                return parent;\n            while (m_nodes[child].children_end != pos)\n                child = m_nodes[child].children_end;\n            return child;\n        }\n\n        ArrayView<const ParsedRegex::Node> m_nodes;\n        Index m_pos;\n        Index m_end;\n    };\n\n    Iterator begin() const { return {m_parsed_regex.nodes, m_index}; }\n    Sentinel end() const { return {}; }\n\n    const ParsedRegex& m_parsed_regex;\n    const Index m_index;\n};\n\n\n// Recursive descent parser based on naming used in the ECMAScript\n// standard, although the syntax is not fully compatible.\nstruct RegexParser\n{\n    static ParsedRegex parse(StringView re) { return RegexParser{re}.m_parsed_regex; }\n\nprivate:\n    RegexParser(StringView re)\n        : m_regex{re}, m_pos{re.begin(), re}\n    {\n        m_parsed_regex.capture_count = 1;\n        m_parsed_regex.nodes.reserve((size_t)re.length());\n        NodeIndex root = disjunction(0);\n        kak_assert(root == 0);\n    }\n\n    struct InvalidPolicy\n    {\n        Codepoint operator()(Codepoint cp) const { throw regex_error{\"Invalid utf8 in regex\"}; }\n    };\n\n    enum class Flags\n    {\n        None              = 0,\n        IgnoreCase        = 1 << 0,\n        DotMatchesNewLine = 1 << 1,\n    };\n    friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; }\n\n    using Iterator = utf8::iterator<const char*, const char*, Codepoint, int, InvalidPolicy>;\n    using NodeIndex = ParsedRegex::NodeIndex;\n\n    NodeIndex disjunction(uint32_t capture = -1)\n    {\n        NodeIndex index = add_node(ParsedRegex::Alternation, capture);\n        while (true)\n        {\n            alternative(ParsedRegex::Sequence);\n            if (at_end() or *m_pos != '|')\n                break;\n            ++m_pos;\n        }\n        get_node(index).children_end = m_parsed_regex.nodes.size();\n\n        return index;\n    }\n\n    NodeIndex alternative(ParsedRegex::Op op)\n    {\n        NodeIndex index = add_node(op);\n        while (term())\n        {}\n        get_node(index).children_end = m_parsed_regex.nodes.size();\n\n        return index;\n    }\n\n    Optional<NodeIndex> term()\n    {\n        while (modifiers()) // read all modifiers\n        {}\n        if (auto node = assertion())\n            return node;\n        if (auto node = atom())\n        {\n            get_node(*node).quantifier = quantifier();\n            return node;\n        }\n        return {};\n    }\n\n    bool modifiers()\n    {\n        auto it = m_pos.base();\n        if (m_regex.end() - it >= 4 and *it++ == '(' and *it++ == '?')\n        {\n            while (true)\n            {\n                auto m = *it++;\n                switch (m)\n                {\n                    case 'i': m_flags |= Flags::IgnoreCase; break;\n                    case 'I': m_flags &= ~Flags::IgnoreCase; break;\n                    case 's': m_flags |= Flags::DotMatchesNewLine; break;\n                    case 'S': m_flags &= ~Flags::DotMatchesNewLine; break;\n                    case ')':\n                        m_pos = Iterator{it, m_regex};\n                        return true;\n                    default: return false;\n                }\n            }\n        }\n        return false;\n    }\n\n    Optional<NodeIndex> assertion()\n    {\n        if (at_end())\n            return {};\n\n        switch (*m_pos)\n        {\n            case '^': ++m_pos; return add_node(ParsedRegex::LineStart);\n            case '$': ++m_pos; return add_node(ParsedRegex::LineEnd);\n            case '\\\\':\n                if (m_pos+1 == m_regex.end())\n                    return {};\n                switch (*(m_pos+1))\n                {\n                    case 'b': m_pos += 2; return add_node(ParsedRegex::WordBoundary);\n                    case 'B': m_pos += 2; return add_node(ParsedRegex::NotWordBoundary);\n                    case 'A': m_pos += 2; return add_node(ParsedRegex::SubjectBegin);\n                    case 'z': m_pos += 2; return add_node(ParsedRegex::SubjectEnd);\n                    case 'K': m_pos += 2; return add_node(ParsedRegex::ResetStart);\n                }\n                break;\n            case '(':\n            {\n                auto it = m_pos.base()+1;\n                if (m_regex.end() - it <= 2 or *it++ != '?')\n                    return {};\n\n                ParsedRegex::Op op;\n                switch (*it++)\n                {\n                    case '=': op = ParsedRegex::LookAhead; break;\n                    case '!': op = ParsedRegex::NegativeLookAhead; break;\n                    case '<':\n                    {\n                        switch (*it++)\n                        {\n                            case '=': op = ParsedRegex::LookBehind; break;\n                            case '!': op = ParsedRegex::NegativeLookBehind; break;\n                            default: return {};\n                        }\n                        break;\n                    }\n                    default: return {};\n                }\n                m_pos = Iterator{it, m_regex};\n                NodeIndex lookaround = alternative(op);\n                if (auto end_pos = m_pos; at_end() or *m_pos++ != ')')\n                {\n                    if (*end_pos == '|')\n                        parse_error(\"Alternations cannot be used in lookarounds\");\n                    parse_error(\"unclosed parenthesis\");\n                }\n                validate_lookaround(lookaround);\n                return lookaround;\n            }\n        }\n        return {};\n    }\n\n    Optional<NodeIndex> atom()\n    {\n        if (at_end())\n            return {};\n\n        switch (const Codepoint cp = *m_pos)\n        {\n            case '.':\n                ++m_pos;\n                return add_node((m_flags & Flags::DotMatchesNewLine) ? ParsedRegex::AnyChar : ParsedRegex::AnyCharExceptNewLine);\n            case '(':\n            {\n                uint32_t capture_group = -1;\n                const char* it = (++m_pos).base();\n                if (m_regex.end() - it < 2 or *it++ != '?')\n                    capture_group = m_parsed_regex.capture_count++;\n                else if (*it == ':')\n                    m_pos = Iterator{++it, m_regex};\n                else if (*it == '<')\n                {\n                    const auto name_start = ++it;\n                    while (it != m_regex.end() and is_word(*it))\n                        ++it;\n                    if (it == m_regex.end() or *it != '>')\n                        parse_error(\"named captures should be only ascii word characters\");\n                    capture_group = m_parsed_regex.capture_count++;\n                    m_parsed_regex.named_captures.push_back({{name_start, it}, capture_group});\n                    m_pos = Iterator{++it, m_regex};\n                }\n\n                NodeIndex content = disjunction(capture_group);\n                if (at_end() or *m_pos++ != ')')\n                    parse_error(\"unclosed parenthesis\");\n                return content;\n            }\n            case '\\\\':\n                ++m_pos;\n                return atom_escape();\n            case '[':\n                ++m_pos;\n                return character_class();\n            case '|': case ')':\n                return {};\n            default:\n                if (contains(StringView{\"^$.*+?[]{}\"}, cp) or (cp >= 0xF0000 and cp <= 0xFFFFF))\n                    parse_error(format(\"unexpected '{}'\", cp));\n                ++m_pos;\n                return add_node(ParsedRegex::Literal, cp);\n        }\n    }\n\n    Codepoint read_hex(size_t count)\n    {\n        Codepoint res = 0;\n        for (int i = 0; i < count; ++i)\n        {\n            if (at_end())\n                parse_error(\"unterminated hex sequence\");\n            Codepoint digit = *m_pos++;\n            Codepoint digit_value;\n            if ('0' <= digit and digit <= '9')\n                digit_value = digit - '0';\n            else if ('a' <= digit and digit <= 'f')\n                digit_value = 0xa + digit - 'a';\n            else if ('A' <= digit and digit <= 'F')\n                digit_value = 0xa + digit - 'A';\n            else\n                parse_error(format(\"invalid hex digit '{}'\", digit));\n\n            res = res * 16 + digit_value;\n        }\n        return res;\n    }\n\n    NodeIndex atom_escape()\n    {\n        const Codepoint cp = *m_pos++;\n\n        if (cp == 'Q')\n        {\n            auto escaped_sequence = add_node(ParsedRegex::Sequence);\n            constexpr StringView end_mark{\"\\\\E\"};\n\n            auto quote_end = std::search(m_pos.base(), m_regex.end(), end_mark.begin(), end_mark.end());\n            while (m_pos != quote_end)\n                add_node(ParsedRegex::Literal, *m_pos++);\n            get_node(escaped_sequence).children_end = m_parsed_regex.nodes.size();\n\n            if (quote_end != m_regex.end())\n                m_pos += 2;\n\n            return escaped_sequence;\n        }\n\n        // CharacterClassEscape\n        auto class_it = find_if(character_class_escapes, [cp](auto& c) { return c.cp == cp; });\n        if (class_it != std::end(character_class_escapes))\n            return add_node(ParsedRegex::CharType, (Codepoint)class_it->ctype);\n\n        // CharacterEscape\n        for (auto& control : control_escapes)\n        {\n            if (control.name == cp)\n                return add_node(ParsedRegex::Literal, control.value);\n        }\n\n        if (cp == '0')\n            return add_node(ParsedRegex::Literal, '\\0');\n        else if (cp == 'c')\n        {\n            if (at_end())\n                parse_error(\"unterminated control escape\");\n            Codepoint ctrl = *m_pos++;\n            if (('a' <= ctrl and ctrl <= 'z') or ('A' <= ctrl and ctrl <= 'Z'))\n                return add_node(ParsedRegex::Literal, ctrl % 32);\n            parse_error(format(\"Invalid control escape character '{}'\", ctrl));\n        }\n        else if (cp == 'x')\n            return add_node(ParsedRegex::Literal, read_hex(2));\n        else if (cp == 'u')\n            return add_node(ParsedRegex::Literal, read_hex(6));\n\n        if (contains(\"^$\\\\.*+?()[]{}|\", cp)) // SyntaxCharacter\n            return add_node(ParsedRegex::Literal, cp);\n        parse_error(format(\"unknown atom escape '{}'\", cp));\n    }\n\n    static void normalize_ranges(Vector<CharacterClass::Range, MemoryDomain::Regex>& ranges)\n    {\n        if (ranges.empty())\n            return;\n\n        // Sort ranges so that we can use binary search\n        std::sort(ranges.begin(), ranges.end(),\n                  [](auto& lhs, auto& rhs) { return lhs.min < rhs.min; });\n\n        // merge overlapping ranges\n        auto pos = ranges.begin();\n        for (auto next = pos+1; next != ranges.end(); ++next)\n        {\n            if (pos->max + 1 >= next->min)\n            {\n                if (next->max > pos->max)\n                    pos->max = next->max;\n            }\n            else\n                *++pos = *next;\n        }\n        ranges.erase(pos+1, ranges.end());\n    }\n\n    NodeIndex character_class()\n    {\n        CharacterClass character_class;\n\n        character_class.ignore_case = (m_flags & Flags::IgnoreCase);\n        character_class.negative = m_pos != m_regex.end() and *m_pos == '^';\n        if (character_class.negative)\n            ++m_pos;\n\n        while (m_pos != m_regex.end() and *m_pos != ']')\n        {\n            auto cp = *m_pos++;\n            if (cp == '-')\n            {\n                character_class.ranges.push_back({ '-', '-' });\n                continue;\n            }\n\n            if (at_end())\n                break;\n\n            auto read_escaped_char = [this]() {\n                Codepoint cp = *m_pos++;\n                auto it = find_if(control_escapes, [cp](auto&& t) { return t.name == cp; });\n                if (it != std::end(control_escapes))\n                    return it->value;\n                if (cp == 'x')\n                    return read_hex(2);\n                if (cp == 'u')\n                    return read_hex(6);\n                if (not contains(\"^$\\\\.*+?()[]{}|-\", cp)) // SyntaxCharacter and -\n                    parse_error(format(\"unknown character class escape '{}'\", cp));\n                return cp;\n            };\n\n            if (cp == '\\\\')\n            {\n                auto it = find_if(character_class_escapes,\n                                  [cp = *m_pos](auto&& t) { return t.cp == cp; });\n                if (it != std::end(character_class_escapes))\n                {\n                    character_class.ctypes |= it->ctype;\n                    ++m_pos;\n                    continue;\n                }\n                else // its an escaped character\n                    cp = read_escaped_char();\n            }\n\n            CharacterClass::Range range = { cp, cp };\n            if (*m_pos == '-')\n            {\n                if (++m_pos == m_regex.end())\n                    break;\n                if (*m_pos != ']')\n                {\n                    cp = *m_pos++;\n                    if (cp == '\\\\')\n                        cp = read_escaped_char();\n                    range.max = cp;\n                    if (range.min > range.max)\n                        parse_error(\"invalid range specified\");\n                }\n                else\n                {\n                    character_class.ranges.push_back(range);\n                    range = { '-', '-' };\n                }\n            }\n            character_class.ranges.push_back(range);\n        }\n        if (at_end())\n            parse_error(\"unclosed character class\");\n        ++m_pos;\n\n        if (not character_class.ignore_case)\n        {\n            bool could_ignore_case = true;\n            for (const auto& [min, max] : character_class.ranges)\n            {\n                if (not contains(character_class.ranges, CharacterClass::Range{to_lower(min), to_lower(max)}) or\n                    not contains(character_class.ranges, CharacterClass::Range{to_upper(min), to_upper(max)}))\n                    could_ignore_case = false;\n            }\n            character_class.ignore_case = could_ignore_case;\n        }\n\n        if (character_class.ignore_case)\n        {\n            for (auto& range : character_class.ranges)\n            {\n                range.min = to_lower(range.min);\n                range.max = to_lower(range.max);\n            }\n        }\n\n        normalize_ranges(character_class.ranges);\n\n        // Optimize the relatively common case of using a character class to\n        // escape a character, such as [*]\n        if (character_class.ctypes == CharacterType::None and not character_class.negative and\n            character_class.ranges.size() == 1 and\n            character_class.ranges.front().min == character_class.ranges.front().max)\n            return add_node(ParsedRegex::Literal, character_class.ranges.front().min, {1,1}, character_class.ignore_case);\n\n        if (character_class.ctypes != CharacterType::None and not character_class.negative and\n            character_class.ranges.empty())\n            return add_node(ParsedRegex::CharType, (Codepoint)character_class.ctypes);\n\n        auto it = std::find(m_parsed_regex.character_classes.begin(), m_parsed_regex.character_classes.end(), character_class);\n        auto class_id = it - m_parsed_regex.character_classes.begin();\n        if (it == m_parsed_regex.character_classes.end())\n            m_parsed_regex.character_classes.push_back(std::move(character_class));\n\n        return add_node(ParsedRegex::CharClass, class_id);\n    }\n\n    ParsedRegex::Quantifier quantifier()\n    {\n        if (at_end())\n            return {1, 1};\n\n        constexpr int max_repeat = 1000;\n        auto read_bound = [&]() -> Optional<int16_t> {\n            int16_t res = 0;\n            for (auto begin = m_pos; m_pos != m_regex.end(); ++m_pos)\n            {\n                const auto cp = *m_pos;\n                if (cp < '0' or cp > '9')\n                    return m_pos == begin ? Optional<int16_t>{} : res;\n                res = res * 10 + cp - '0';\n                if (res > max_repeat)\n                    parse_error(format(\"Explicit quantifier is too big, maximum is {}\", max_repeat));\n            }\n            return res;\n        };\n\n        auto check_greedy = [&]() {\n            if (at_end() or *m_pos != '?')\n                return true;\n            ++m_pos;\n            return false;\n        };\n\n        switch (*m_pos)\n        {\n            case '*': ++m_pos; return {0, ParsedRegex::Quantifier::infinite, check_greedy()};\n            case '+': ++m_pos; return {1, ParsedRegex::Quantifier::infinite, check_greedy()};\n            case '?': ++m_pos; return {0, 1, check_greedy()};\n            case '{':\n            {\n                ++m_pos;\n                const int16_t min = read_bound().value_or(int16_t{});\n                int16_t max = min;\n                if (*m_pos == ',')\n                {\n                    ++m_pos;\n                    max = read_bound().value_or(ParsedRegex::Quantifier::infinite);\n                }\n                if (*m_pos++ != '}')\n                   parse_error(\"expected closing bracket\");\n                return {min, max, check_greedy()};\n            }\n            default: return {1, 1};\n        }\n    }\n\n    NodeIndex add_node(ParsedRegex::Op op, Codepoint value = -1, ParsedRegex::Quantifier quantifier = {1, 1}, bool ignore_case = false)\n    {\n        constexpr auto max_nodes = std::numeric_limits<int16_t>::max();\n        const NodeIndex res = m_parsed_regex.nodes.size();\n        if (res == max_nodes)\n            parse_error(format(\"regex parsed to more than {} ast nodes\", max_nodes));\n        const NodeIndex next = res+1;\n        m_parsed_regex.nodes.push_back({op, ignore_case or (m_flags & Flags::IgnoreCase), next, value, quantifier});\n        return res;\n    }\n\n    ParsedRegex::Node& get_node(NodeIndex index)\n    {\n        return m_parsed_regex.nodes[index];\n    }\n\n    bool at_end() const { return m_pos == m_regex.end(); }\n\n    [[gnu::noreturn]]\n    void parse_error(StringView error) const\n    {\n        throw regex_error(format(\"regex parse error: {} at '{}<<<HERE>>>{}'\", error,\n                                 StringView{m_regex.begin(), m_pos.base()},\n                                 StringView{m_pos.base(), m_regex.end()}));\n    }\n\n    void validate_lookaround(NodeIndex index)\n    {\n        using Lookaround = CompiledRegex::Lookaround;\n        for (auto child_index : Children<>{m_parsed_regex, index})\n        {\n            auto& child = get_node(child_index);\n            if (child.op != ParsedRegex::Literal and child.op != ParsedRegex::CharClass and\n                child.op != ParsedRegex::CharType and child.op != ParsedRegex::AnyChar and\n                child.op != ParsedRegex::AnyCharExceptNewLine)\n                parse_error(\"Lookaround can only contain literals, any chars or character classes\");\n            if (child.op == ParsedRegex::Literal and\n                to_underlying(Lookaround::OpBegin) <= child.value and\n                child.value < to_underlying(Lookaround::OpEnd))\n                parse_error(\"Lookaround does not support literals codepoint between 0xF0000 and 0xFFFFD\");\n            if (child.quantifier != ParsedRegex::Quantifier{1, 1})\n                parse_error(\"Quantifiers cannot be used in lookarounds\");\n        }\n    }\n\n    ParsedRegex m_parsed_regex;\n    StringView m_regex;\n    Iterator m_pos;\n\n    Flags m_flags = Flags::DotMatchesNewLine;\n\n    static constexpr struct CharacterClassEscape {\n        Codepoint cp;\n        CharacterType ctype;\n    } character_class_escapes[] = {\n        { 'd', CharacterType::Digit },                { 'D', CharacterType::NotDigit },\n        { 'w', CharacterType::Word },                 { 'W', CharacterType::NotWord },\n        { 's', CharacterType::Whitespace },           { 'S', CharacterType::NotWhitespace },\n        { 'h', CharacterType::HorizontalWhitespace }, { 'H', CharacterType::NotHorizontalWhitespace },\n    };\n\n    static constexpr struct ControlEscape {\n        Codepoint name;\n        Codepoint value;\n    } control_escapes[] = {\n        { 'f', '\\f' },\n        { 'n', '\\n' },\n        { 'r', '\\r' },\n        { 't', '\\t' },\n        { 'v', '\\v' }\n    };\n};\n\nconstexpr RegexParser::CharacterClassEscape RegexParser::character_class_escapes[];\nconstexpr RegexParser::ControlEscape RegexParser::control_escapes[];\n\nstruct RegexCompiler\n{\n    using OpIndex = int16_t;\n\n    RegexCompiler(ParsedRegex&& parsed_regex, RegexCompileFlags flags)\n        : m_flags(flags), m_parsed_regex{parsed_regex}\n    {\n        kak_assert(not (flags & RegexCompileFlags::NoForward) or flags & RegexCompileFlags::Backward);\n        // Approximation of the number of instructions generated\n        m_program.instructions.reserve((parsed_regex.nodes.size() + 1)\n                                       * (((flags & RegexCompileFlags::Backward) and\n                                           not (flags & RegexCompileFlags::NoForward)) ? 2 : 1));\n\n        if (not (flags & RegexCompileFlags::NoForward))\n        {\n            m_program.forward_start_desc = compute_start_desc<RegexMode::Forward>();\n            compile_node<RegexMode::Forward>(0);\n            optimize(0, m_program.instructions.size());\n            push_inst(CompiledRegex::Match);\n        }\n\n        if (flags & RegexCompileFlags::Backward)\n        {\n            m_program.first_backward_inst = m_program.instructions.size();\n            m_program.backward_start_desc = compute_start_desc<RegexMode::Backward>();\n            compile_node<RegexMode::Backward>(0);\n            optimize(m_program.first_backward_inst, m_program.instructions.size());\n            push_inst(CompiledRegex::Match);\n        }\n        else\n            m_program.first_backward_inst = -1;\n\n        m_program.character_classes = std::move(m_parsed_regex.character_classes);\n        m_program.named_captures = std::move(m_parsed_regex.named_captures);\n        m_program.save_count = m_parsed_regex.capture_count * 2;\n    }\n\n    CompiledRegex get_compiled_regex() { return std::move(m_program); }\n\nprivate:\n\n    template<RegexMode direction>\n    [[gnu::noinline]]\n    OpIndex compile_node_inner(ParsedRegex::NodeIndex index)\n    {\n        auto& node = get_node(index);\n\n        const OpIndex start_pos = op_count();\n        const bool ignore_case = node.ignore_case;\n\n        const bool save = (node.op == ParsedRegex::Alternation or node.op == ParsedRegex::Sequence) and\n                          (node.value == 0 or (node.value != -1 and not (m_flags & RegexCompileFlags::NoSubs)));\n        constexpr bool forward = direction == RegexMode::Forward;\n        if (save)\n            push_inst(CompiledRegex::Save, {.save_index = int16_t(node.value * 2 + (forward ? 0 : 1))});\n\n        Vector<uint32_t> goto_inner_end_offsets;\n        switch (node.op)\n        {\n            case ParsedRegex::Literal:\n                push_inst(CompiledRegex::Literal, {.literal={.codepoint=ignore_case ? to_lower(node.value) : node.value, .ignore_case=ignore_case}});\n                break;\n            case ParsedRegex::AnyChar:\n                push_inst(CompiledRegex::AnyChar);\n                break;\n            case ParsedRegex::AnyCharExceptNewLine:\n                push_inst(CompiledRegex::AnyCharExceptNewLine);\n                break;\n            case ParsedRegex::CharClass:\n                if (auto& char_class = m_parsed_regex.character_classes[node.value];\n                    char_class.ranges.size() == 1 and char_class.ctypes == CharacterType::None and\n                    char_class.ranges[0].max <= std::numeric_limits<uint8_t>::max())\n                    push_inst(CompiledRegex::CharRange, {.range={.min=uint8_t(char_class.ranges[0].min),\n                                                                 .max=uint8_t(char_class.ranges[0].max),\n                                                                 .ignore_case=char_class.ignore_case,\n                                                                 .negative=char_class.negative}});\n                else\n                    push_inst(CompiledRegex::CharClass, {.character_class_index=int16_t(node.value)});\n                break;\n            case ParsedRegex::CharType:\n                push_inst(CompiledRegex::CharType, {.character_type=CharacterType{(unsigned char)node.value}});\n                break;\n            case ParsedRegex::Sequence:\n            {\n                for (auto child : Children<direction>{m_parsed_regex, index})\n                    compile_node<direction>(child);\n                break;\n            }\n            case ParsedRegex::Alternation:\n            {\n                for (auto child : Children<>{m_parsed_regex, index})\n                {\n                    if (child != index+1)\n                        push_inst(CompiledRegex::Split);\n                }\n                auto split_pos = op_count();\n\n                const auto end = node.children_end;\n                for (auto child : Children<>{m_parsed_regex, index})\n                {\n                    auto node = compile_node<direction>(child);\n                    if (child != index+1)\n                    {\n                        --split_pos;\n                        m_program.instructions[split_pos].param.split = {.offset = offset(node, split_pos), .prioritize_parent = true};\n                    }\n                    if (get_node(child).children_end != end)\n                    {\n                        auto jump = push_inst(CompiledRegex::Jump);\n                        goto_inner_end_offsets.push_back(jump);\n                    }\n                }\n                break;\n            }\n            case ParsedRegex::LookAhead:\n            case ParsedRegex::NegativeLookAhead:\n                push_inst(CompiledRegex::LookAround, {.lookaround={\n                    .index=push_lookaround<RegexMode::Forward>(index, ignore_case),\n                    .ahead=true,\n                    .positive=node.op == ParsedRegex::LookAhead,\n                    .ignore_case=ignore_case}});\n                break;\n            case ParsedRegex::LookBehind:\n            case ParsedRegex::NegativeLookBehind:\n                push_inst(CompiledRegex::LookAround, {.lookaround={\n                    .index=push_lookaround<RegexMode::Backward>(index, ignore_case),\n                    .ahead=false,\n                    .positive=node.op == ParsedRegex::LookBehind,\n                    .ignore_case=ignore_case}});\n                break;\n            case ParsedRegex::LineStart:\n                push_inst(CompiledRegex::LineAssertion, {.line_start=true});\n                break;\n            case ParsedRegex::LineEnd:\n                push_inst(CompiledRegex::LineAssertion, {.line_start=false});\n                break;\n            case ParsedRegex::WordBoundary:\n                push_inst(CompiledRegex::WordBoundary, {.word_boundary_positive=true});\n                break;\n            case ParsedRegex::NotWordBoundary:\n                push_inst(CompiledRegex::WordBoundary, {.word_boundary_positive=false});\n                break;\n            case ParsedRegex::SubjectBegin:\n                push_inst(CompiledRegex::SubjectAssertion, {.subject_begin=true});\n                break;\n            case ParsedRegex::SubjectEnd:\n                push_inst(CompiledRegex::SubjectAssertion, {.subject_begin=false});\n                break;\n            case ParsedRegex::ResetStart:\n                push_inst(CompiledRegex::Save, {.save_index=0});\n                break;\n        }\n\n        for (auto& index : goto_inner_end_offsets)\n            m_program.instructions[index].param.jump_offset = offset(op_count(), index);\n\n        if (save)\n            push_inst(CompiledRegex::Save, {.save_index=int16_t(node.value * 2 + (forward ? 1 : 0))});\n\n        return start_pos;\n    }\n\n    OpIndex op_count() const\n    {\n        return static_cast<OpIndex>(m_program.instructions.size());\n    }\n\n    static OpIndex offset(OpIndex to, OpIndex from)\n    {\n        return static_cast<OpIndex>(to - from);\n    }\n\n    template<RegexMode direction>\n    OpIndex compile_node(ParsedRegex::NodeIndex index)\n    {\n        auto& node = get_node(index);\n\n        const OpIndex start_pos = op_count();\n        Vector<OpIndex> goto_ends;\n\n        auto& quantifier = node.quantifier;\n\n        if (quantifier.allows_none())\n        {\n            auto split_pos = push_inst(CompiledRegex::Split, {.split={.offset=0, .prioritize_parent=quantifier.greedy}});\n            goto_ends.push_back(split_pos);\n        }\n\n        auto inner_pos = compile_node_inner<direction>(index);\n        // Write the node multiple times when we have a min count quantifier\n        for (int i = 1; i < quantifier.min; ++i)\n            inner_pos = compile_node_inner<direction>(index);\n\n        if (quantifier.allows_infinite_repeat())\n            push_inst(CompiledRegex::Split, {.split = {.offset=offset(inner_pos, op_count()), .prioritize_parent=not quantifier.greedy}});\n        // Write the node as an optional match for the min -> max counts\n        else for (int i = std::max((int16_t)1, quantifier.min); // STILL UGLY !\n                  i < quantifier.max; ++i)\n        {\n            auto split_pos = push_inst(CompiledRegex::Split, {.split={.offset=0, .prioritize_parent=quantifier.greedy}});\n            goto_ends.push_back(split_pos);\n            compile_node_inner<direction>(index);\n        }\n\n        for (auto index : goto_ends)\n            m_program.instructions[index].param.split.offset = offset(op_count(), index);\n\n        return start_pos;\n    }\n\n    OpIndex push_inst(CompiledRegex::Op op, CompiledRegex::Param param = {})\n    {\n        constexpr auto max_instructions = std::numeric_limits<OpIndex>::max();\n        const auto res = op_count();\n        if (res >= max_instructions)\n            throw regex_error(format(\"regex compiled to more than {} instructions\", max_instructions));\n        m_program.instructions.push_back({ op, 0, param });\n        return OpIndex(res);\n    }\n\n    template<RegexMode direction>\n    int16_t push_lookaround(ParsedRegex::NodeIndex index, bool ignore_case)\n    {\n        using Lookaround = CompiledRegex::Lookaround;\n\n        const int16_t res = m_program.lookarounds.size();\n        for (auto child : Children<direction>{m_parsed_regex, index})\n        {\n            auto& character = get_node(child);\n            if (character.op == ParsedRegex::Literal)\n                m_program.lookarounds.push_back(\n                    static_cast<Lookaround>(ignore_case ? to_lower(character.value) : character.value));\n            else if (character.op == ParsedRegex::AnyChar)\n                m_program.lookarounds.push_back(Lookaround::AnyChar);\n            else if (character.op == ParsedRegex::AnyCharExceptNewLine)\n                m_program.lookarounds.push_back(Lookaround::AnyCharExceptNewLine);\n            else if (character.op == ParsedRegex::CharClass)\n                m_program.lookarounds.push_back(static_cast<Lookaround>(to_underlying(Lookaround::CharacterClass) + character.value));\n            else if (character.op == ParsedRegex::CharType)\n                m_program.lookarounds.push_back(static_cast<Lookaround>(to_underlying(Lookaround::CharacterType) | character.value));\n            else\n                kak_assert(false);\n        }\n        m_program.lookarounds.push_back(Lookaround::EndOfLookaround);\n        return res;\n    }\n\n    // Mutate start_desc with informations on which Codepoint could start a match.\n    // Returns true if the subsequent nodes are still relevant for computing the\n    // start desc\n    template<RegexMode direction>\n    bool compute_start_desc(ParsedRegex::NodeIndex index,\n                             CompiledRegex::StartDesc& start_desc) const\n    {\n        // fill all bytes that mark the start of an utf8 multi byte sequence\n        auto add_multi_byte_utf8 = [&] {\n            std::fill(start_desc.map + 0b11000000, start_desc.map + 0b11111000, true);\n        };\n        static constexpr Codepoint single_byte_limit = 128;\n\n        auto& node = get_node(index);\n        switch (node.op)\n        {\n            case ParsedRegex::Literal:\n                if (node.value < single_byte_limit)\n                {\n                    if (node.ignore_case)\n                    {\n                        start_desc.map[to_lower(node.value)] = true;\n                        start_desc.map[to_upper(node.value)] = true;\n                    }\n                    else\n                        start_desc.map[node.value] = true;\n                }\n                else\n                    add_multi_byte_utf8();\n                return node.quantifier.allows_none();\n            case ParsedRegex::AnyChar:\n                if (start_desc.offset + node.quantifier.max <= CompiledRegex::StartDesc::OffsetLimits::max())\n                {\n                    start_desc.offset += node.quantifier.max;\n                    return true;\n                }\n                for (auto& b : start_desc.map)\n                    b = true;\n               return node.quantifier.allows_none();\n            case ParsedRegex::AnyCharExceptNewLine:\n                if (start_desc.offset + node.quantifier.max <= CompiledRegex::StartDesc::OffsetLimits::max())\n                {\n                    start_desc.offset += node.quantifier.max;\n                    return true;\n                }\n                for (Codepoint cp = 0; cp < single_byte_limit; ++cp)\n                {\n                    if (cp != '\\n')\n                        start_desc.map[cp] = true;\n                }\n               return node.quantifier.allows_none();\n            case ParsedRegex::CharClass:\n            {\n                auto& character_class = m_parsed_regex.character_classes[node.value];\n                if (character_class.ctypes == CharacterType::None and\n                    not character_class.negative and\n                    not character_class.ignore_case)\n                {\n                    for (auto& range : character_class.ranges)\n                    {\n                        const auto clamp = [](Codepoint cp) { return std::min(single_byte_limit, cp); };\n                        for (auto cp = clamp(range.min), end = clamp(range.max + 1); cp < end; ++cp)\n                            start_desc.map[cp] = true;\n                        if (range.max >= single_byte_limit)\n                            add_multi_byte_utf8();\n                    }\n                }\n                else\n                {\n                    for (Codepoint cp = 0; cp < single_byte_limit; ++cp)\n                    {\n                        if (start_desc.map[cp] or character_class.matches(cp))\n                            start_desc.map[cp] = true;\n                    }\n                }\n                add_multi_byte_utf8();\n                return node.quantifier.allows_none();\n            }\n            case ParsedRegex::CharType:\n            {\n                const CharacterType ctype = (CharacterType)node.value;\n                for (Codepoint cp = 0; cp < single_byte_limit; ++cp)\n                {\n                    if (is_ctype(ctype, cp))\n                        start_desc.map[cp] = true;\n                }\n                add_multi_byte_utf8();\n                return node.quantifier.allows_none();\n            }\n            case ParsedRegex::Sequence:\n            {\n                for (auto child : Children<direction>{m_parsed_regex, index})\n                {\n                    if (not compute_start_desc<direction>(child, start_desc))\n                        return node.quantifier.allows_none();\n                }\n                return true;\n            }\n            case ParsedRegex::Alternation:\n            {\n                bool all_consumed = not node.quantifier.allows_none();\n                for (auto child : Children<>{m_parsed_regex, index})\n                {\n                    if (compute_start_desc<direction>(child, start_desc))\n                        all_consumed = false;\n                }\n                return not all_consumed;\n            }\n            case ParsedRegex::LineStart:\n            case ParsedRegex::LineEnd:\n            case ParsedRegex::WordBoundary:\n            case ParsedRegex::NotWordBoundary:\n            case ParsedRegex::SubjectBegin:\n            case ParsedRegex::SubjectEnd:\n            case ParsedRegex::ResetStart:\n            case ParsedRegex::LookAhead:\n            case ParsedRegex::LookBehind:\n            case ParsedRegex::NegativeLookAhead:\n            case ParsedRegex::NegativeLookBehind:\n                return true;\n        }\n        return false;\n    }\n\n    template<RegexMode direction>\n    [[gnu::noinline]]\n    UniquePtr<CompiledRegex::StartDesc> compute_start_desc() const\n    {\n        CompiledRegex::StartDesc start_desc{};\n        if (compute_start_desc<direction>(0, start_desc) or\n            not contains(start_desc.map, false))\n            return nullptr;\n\n        if (std::count(std::begin(start_desc.map), std::end(start_desc.map), true) == 1)\n            start_desc.start_byte = find(start_desc.map, true) - std::begin(start_desc.map);\n\n        return make_unique_ptr<CompiledRegex::StartDesc>(start_desc);\n    }\n\n    void optimize(size_t begin, size_t end)\n    {\n        if (not (m_flags & RegexCompileFlags::Optimize))\n            return;\n\n        auto is_jump = [](CompiledRegex::Op op) { return op >= CompiledRegex::Op::Jump and op <= CompiledRegex::Op::Split; };\n        for (auto i = begin; i < end; ++i)\n        {\n            auto& inst = m_program.instructions[i];\n            if (is_jump(inst.op))\n                m_program.instructions[i + inst.param.jump_offset].last_step = 0xffff; // tag as jump target\n        }\n\n        for (auto block_begin = begin; block_begin < end; )\n        {\n            auto block_end = block_begin + 1;\n            while (block_end < end and\n                   not is_jump(m_program.instructions[block_end].op) and\n                   m_program.instructions[block_end].last_step != 0xffff)\n                ++block_end;\n            peephole_optimize(block_begin, block_end);\n            block_begin = block_end;\n        }\n    }\n\n    void peephole_optimize(size_t begin, size_t end)\n    {\n        // Move saves after all assertions on the same character\n        auto is_assertion = [](CompiledRegex::Op op) { return op >= CompiledRegex::LineAssertion; };\n        for (auto i = begin, j = begin + 1; j < end; ++i, ++j)\n        {\n            if (m_program.instructions[i].op == CompiledRegex::Save and\n                is_assertion(m_program.instructions[j].op))\n                std::swap(m_program.instructions[i], m_program.instructions[j]);\n        }\n    }\n\n    const ParsedRegex::Node& get_node(ParsedRegex::NodeIndex index) const\n    {\n        return m_parsed_regex.nodes[index];\n    }\n\n    CompiledRegex m_program;\n    RegexCompileFlags m_flags;\n    ParsedRegex& m_parsed_regex;\n};\n\n}\n\nString dump_regex(const CompiledRegex& program)\n{\n    String res;\n    int index = 0;\n    for (auto& inst : program.instructions)\n    {\n        char buf[20];\n        format_to(buf, \" {:03}     \", index);\n        res += buf;\n        switch (inst.op)\n        {\n            case CompiledRegex::Literal:\n                res += format(\"literal {}{}\\n\", inst.param.literal.ignore_case ? \"(ignore case) \" : \"\", inst.param.literal.codepoint);\n                break;\n            case CompiledRegex::AnyChar:\n                res += \"any char\\n\";\n                break;\n            case CompiledRegex::AnyCharExceptNewLine:\n                res += \"anything but newline\\n\";\n                break;\n            case CompiledRegex::CharRange:\n                res += format(\"character range {}[{}{}-{}]\\n\",\n                              inst.param.range.ignore_case ? \"(ignore case) \" : \"\",\n                              inst.param.range.negative ? \"^\" : \"\",\n                              inst.param.range.min, inst.param.range.max);\n                break;\n            case CompiledRegex::CharType:\n                res += format(\"character type {}\\n\", to_underlying(inst.param.character_type));\n                break;\n            case CompiledRegex::CharClass:\n                res += format(\"character class {}\\n\", inst.param.character_class_index);\n                break;\n            case CompiledRegex::Jump:\n                res += format(\"jump {} ({:03})\\n\", inst.param.jump_offset, index + inst.param.jump_offset);\n                break;\n            case CompiledRegex::Split:\n            {\n                res += format(\"split (prioritize {}) {} ({:03})\\n\",\n                              (inst.param.split.prioritize_parent) ? \"parent\" : \"child\",\n                              inst.param.split.offset, index + inst.param.split.offset);\n                break;\n            }\n            case CompiledRegex::Save:\n                res += format(\"save {}\\n\", inst.param.save_index);\n                break;\n            case CompiledRegex::LineAssertion:\n                res += format(\"line {}\\n\", inst.param.line_start ? \"start\" : \"end\");;\n                break;\n            case CompiledRegex::SubjectAssertion:\n                res += format(\"subject {}\\n\", inst.param.subject_begin ? \"begin\" : \"end\");\n                break;\n            case CompiledRegex::WordBoundary:\n                res += format(\"{}word boundary\\n\", inst.param.word_boundary_positive ? \"\" : \"not \");\n                break;\n            case CompiledRegex::LookAround:\n            {\n                String name;\n                name += inst.param.lookaround.positive ? \"\" : \"negative \";\n                name += \"look \";\n                name += inst.param.lookaround.ahead ? \"ahead \" : \"behind \";\n                if (inst.param.lookaround.ignore_case)\n                    name += \" (ignore case)\";\n\n                String str;\n                for (auto it = program.lookarounds.begin() + inst.param.lookaround.index;\n                     *it != CompiledRegex::Lookaround::EndOfLookaround; ++it)\n                    utf8::dump(std::back_inserter(str), to_underlying(*it));\n                res += format(\"{} ({})\\n\", name, str);\n                break;\n            }\n            case CompiledRegex::Match:\n                res += \"match\\n\";\n        }\n        ++index;\n    }\n    auto dump_start_desc = [&](const CompiledRegex::StartDesc& desc, StringView name) {\n        res += name + \" start desc: [\";\n        for (size_t c = 0; c < CompiledRegex::StartDesc::count; ++c)\n        {\n            if (desc.map[c])\n            {\n                if (c < 32)\n                    res += format(\"<0x{}>\", hex(c));\n                else\n                    res += (char)c;\n            }\n        }\n        res += format(\"]+{}\\n\", static_cast<int>(desc.offset));\n    };\n    if (program.forward_start_desc)\n        dump_start_desc(*program.forward_start_desc, \"forward\");\n    if (program.backward_start_desc)\n        dump_start_desc(*program.backward_start_desc, \"backward\");\n    return res;\n}\n\nCompiledRegex compile_regex(StringView re, RegexCompileFlags flags)\n{\n    return RegexCompiler{RegexParser::parse(re), flags}.get_compiled_regex();\n}\n\nbool is_ctype(CharacterType ctype, Codepoint cp)\n{\n    auto check = [&](CharacterType bit, CharacterType not_bit, auto&& func) {\n        return (ctype & (bit | not_bit)) and func(cp) == (bool)(ctype & bit);\n    };\n    return check(CharacterType::Word, CharacterType::NotWord, [](Codepoint cp) { return is_word(cp); }) or\n           check(CharacterType::Whitespace, CharacterType::NotWhitespace, is_blank) or\n           check(CharacterType::HorizontalWhitespace, CharacterType::NotHorizontalWhitespace, is_horizontal_blank) or\n           check(CharacterType::Digit, CharacterType::NotDigit, iswdigit);\n}\n\nnamespace\n{\ntemplate<RegexMode mode = RegexMode::Forward>\nstruct TestVM : CompiledRegex, ThreadedRegexVM<const char*, mode>\n{\n    TestVM(StringView re)\n      : CompiledRegex{compile_regex(re, mode & RegexMode::Forward ? RegexCompileFlags::None : RegexCompileFlags::Backward)},\n        TestVM::ThreadedRegexVM{static_cast<const CompiledRegex&>(*this)}\n    {}\n\n    bool exec(StringView re, RegexExecFlags flags = RegexExecFlags::None)\n    {\n        return TestVM::ThreadedRegexVM::exec(re.begin(), re.end(), re.begin(), re.end(), flags);\n    }\n\n    using TestVM::ThreadedRegexVM::exec;\n};\n}\n\nauto test_regex = UnitTest{[]{\n    {\n        TestVM<> vm{R\"(a*b)\"};\n        kak_assert(vm.exec(\"b\"));\n        kak_assert(vm.exec(\"ab\"));\n        kak_assert(vm.exec(\"aaab\"));\n        kak_assert(not vm.exec(\"acb\"));\n        kak_assert(not vm.exec(\"abc\"));\n        kak_assert(not vm.exec(\"\"));\n    }\n\n    {\n        TestVM<> vm{R\"(^a.*b$)\"};\n        kak_assert(vm.exec(\"afoob\"));\n        kak_assert(vm.exec(\"ab\"));\n        kak_assert(not vm.exec(\"bab\"));\n        kak_assert(not vm.exec(\"\"));\n    }\n\n    {\n        TestVM<> vm{R\"(^(foo|qux|baz)+(bar)?baz$)\"};\n        kak_assert(vm.exec(\"fooquxbarbaz\"));\n        kak_assert(StringView{vm.captures()[2], vm.captures()[3]} == \"qux\");\n        kak_assert(not vm.exec(\"fooquxbarbaze\"));\n        kak_assert(not vm.exec(\"quxbar\"));\n        kak_assert(not vm.exec(\"blahblah\"));\n        kak_assert(vm.exec(\"bazbaz\"));\n        kak_assert(vm.exec(\"quxbaz\"));\n    }\n\n    {\n        TestVM<> vm{R\"(.*\\b(foo|bar)\\b.*)\"};\n        kak_assert(vm.exec(\"qux foo baz\"));\n        kak_assert(StringView{vm.captures()[2], vm.captures()[3]} == \"foo\");\n        kak_assert(not vm.exec(\"quxfoobaz\"));\n        kak_assert(vm.exec(\"bar\"));\n        kak_assert(not vm.exec(\"foobar\"));\n    }\n\n    {\n        TestVM<> vm{R\"((foo|bar))\"};\n        kak_assert(vm.exec(\"foo\"));\n        kak_assert(vm.exec(\"bar\"));\n        kak_assert(not vm.exec(\"foobar\"));\n    }\n\n    {\n        TestVM<> vm{R\"([aA])\"};\n        kak_assert(vm.exec(\"a\"));\n        kak_assert(vm.exec(\"A\"));\n    }\n\n    {\n        TestVM<> vm{R\"(a{3,5}b)\"};\n        kak_assert(not vm.exec(\"aab\"));\n        kak_assert(vm.exec(\"aaab\"));\n        kak_assert(not vm.exec(\"aaaaaab\"));\n        kak_assert(vm.exec(\"aaaaab\"));\n    }\n\n    {\n        TestVM<> vm{R\"(a{3}b)\"};\n        kak_assert(not vm.exec(\"aab\"));\n        kak_assert(vm.exec(\"aaab\"));\n        kak_assert(not vm.exec(\"aaaab\"));\n    }\n\n    {\n        TestVM<> vm{R\"(a{3,}b)\"};\n        kak_assert(not vm.exec(\"aab\"));\n        kak_assert(vm.exec(\"aaab\"));\n        kak_assert(vm.exec(\"aaaaab\"));\n    }\n\n    {\n        TestVM<> vm{R\"(a{,3}b)\"};\n        kak_assert(vm.exec(\"b\"));\n        kak_assert(vm.exec(\"ab\"));\n        kak_assert(vm.exec(\"aaab\"));\n        kak_assert(not vm.exec(\"aaaab\"));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"(f.*a(.*o))\"};\n        kak_assert(vm.exec(\"blahfoobarfoobaz\"));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]} == \"foobarfoo\");\n        kak_assert(StringView{vm.captures()[2], vm.captures()[3]} == \"rfoo\");\n        kak_assert(vm.exec(\"mais que fais la police\"));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]} == \"fais la po\");\n        kak_assert(StringView{vm.captures()[2], vm.captures()[3]} == \" po\");\n    }\n\n    {\n        TestVM<> vm{R\"([àb-dX-Z-]{3,5})\"};\n        kak_assert(vm.exec(\"cà-Y\"));\n        kak_assert(not vm.exec(\"àeY\"));\n        kak_assert(vm.exec(\"dcbàX\"));\n        kak_assert(not vm.exec(\"efg\"));\n    }\n\n    {\n        TestVM<> vm{R\"((a{3,5})a+)\"};\n        kak_assert(vm.exec(\"aaaaaa\", RegexExecFlags::None));\n        kak_assert(StringView{vm.captures()[2], vm.captures()[3]} == \"aaaaa\");\n    }\n\n    {\n        TestVM<> vm{R\"((a{3,5}?)a+)\"};\n        kak_assert(vm.exec(\"aaaaaa\", RegexExecFlags::None));\n        kak_assert(StringView{vm.captures()[2], vm.captures()[3]} == \"aaa\");\n    }\n\n    {\n        TestVM<> vm{R\"((a{3,5}?)a)\"};\n        kak_assert(vm.exec(\"aaaa\"));\n    }\n\n    {\n        TestVM<> vm{R\"(\\d{3})\"};\n        kak_assert(vm.exec(\"123\"));\n        kak_assert(not vm.exec(\"1x3\"));\n    }\n\n    {\n        TestVM<> vm{R\"([-\\d]+)\"};\n        kak_assert(vm.exec(\"123-456\"));\n        kak_assert(not vm.exec(\"123_456\"));\n    }\n\n    {\n        TestVM<> vm{R\"([ \\H]+)\"};\n        kak_assert(vm.exec(\"abc \"));\n        kak_assert(not vm.exec(\"a \\t\"));\n    }\n\n    {\n        TestVM<> vm{R\"(\\Q{}[]*+?\\Ea+)\"};\n        kak_assert(vm.exec(\"{}[]*+?aa\"));\n    }\n\n    {\n        TestVM<> vm{R\"(\\Q...)\"};\n        kak_assert(vm.exec(\"...\"));\n        kak_assert(not vm.exec(\"bla\"));\n    }\n\n    {\n        TestVM<RegexMode::Forward> vm{R\"(foo\\Kbar)\"};\n        kak_assert(vm.exec(\"foobar\"));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]} == \"bar\");\n        kak_assert(not vm.exec(\"bar\"));\n    }\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"(foobaz|foo|foobar)\"};\n        kak_assert(vm.exec(\"foobar\"));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]} == \"foo\");\n    }\n    {\n        TestVM<RegexMode::Forward> vm{R\"((fo+?).*)\"};\n        kak_assert(vm.exec(\"foooo\"));\n        kak_assert(StringView{vm.captures()[2], vm.captures()[3]} == \"fo\");\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"((?=fo[\\w]).)\"};\n        kak_assert(vm.exec(\"barfoo\"));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]} == \"f\");\n    }\n\n    {\n        TestVM<> vm{R\"((?<!f).)\"};\n        kak_assert(vm.exec(\"f\"));\n    }\n\n    {\n        TestVM<> vm{R\"((?!f[oa]o)...)\"};\n        kak_assert(not vm.exec(\"foo\"));\n        kak_assert(vm.exec(\"qux\"));\n    }\n\n    {\n        TestVM<> vm{R\"(...(?<=f\\w.))\"};\n        kak_assert(vm.exec(\"foo\"));\n        kak_assert(not vm.exec(\"qux\"));\n    }\n\n    {\n        TestVM<> vm{R\"(...(?<!foo))\"};\n        kak_assert(not vm.exec(\"foo\"));\n        kak_assert(vm.exec(\"qux\"));\n    }\n\n    {\n        TestVM<> vm{R\"(Foo(?i)f[oB]+)\"};\n        kak_assert(vm.exec(\"FooFOoBb\"));\n    }\n\n    {\n        TestVM<> vm{R\"((?i)[a-z]+)\"};\n        kak_assert(vm.exec(\"ABC\"));\n    }\n\n    {\n        TestVM<> vm{R\"([^\\]]+)\"};\n        kak_assert(not vm.exec(\"a]c\"));\n        kak_assert(vm.exec(\"abc\"));\n    }\n\n    {\n        TestVM<> vm{R\"([^:\\n]+)\"};\n        kak_assert(not vm.exec(\"\\nbc\"));\n        kak_assert(vm.exec(\"abc\"));\n    }\n\n    {\n        TestVM<> vm{R\"((?:foo)+)\"};\n        kak_assert(vm.exec(\"foofoofoo\"));\n        kak_assert(not vm.exec(\"barbarbar\"));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"((?<!\\\\)(?:\\\\\\\\)*\")\"};\n        kak_assert(vm.exec(\"foo\\\"\"));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"($)\"};\n        kak_assert(vm.exec(\"foo\\n\"));\n        kak_assert(*vm.captures()[0] == '\\n');\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{R\"(fo{1,})\"};\n        kak_assert(vm.exec(\"foo1fooo2\"));\n        kak_assert(*vm.captures()[1] == '2');\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{R\"((?<=f)oo(b[ae]r)?(?=baz))\"};\n        kak_assert(vm.exec(\"foobarbazfoobazfooberbaz\"));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]}  == \"oober\");\n        kak_assert(StringView{vm.captures()[2], vm.captures()[3]}  == \"ber\");\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{R\"((baz|boz|foo|qux)(?<!baz)(?<!o))\"};\n        kak_assert(vm.exec(\"quxbozfoobaz\"));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]}  == \"boz\");\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{R\"(foo)\"};\n        kak_assert(vm.exec(\"foofoo\"));\n        kak_assert(*vm.captures()[1]  == 0);\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{R\"($)\"};\n        kak_assert(vm.exec(\"foo\\nbar\\nbaz\\nqux\", RegexExecFlags::NotEndOfLine));\n        kak_assert(StringView{vm.captures()[0]}  == \"\\nqux\");\n        kak_assert(vm.exec(\"foo\\nbar\\nbaz\\nqux\", RegexExecFlags::None));\n        kak_assert(StringView{vm.captures()[0]}  == \"\");\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{R\"(^)\"};\n        kak_assert(not vm.exec(\"foo\", RegexExecFlags::NotBeginOfLine));\n        kak_assert(vm.exec(\"foo\", RegexExecFlags::None));\n        kak_assert(vm.exec(\"foo\\nbar\", RegexExecFlags::None));\n        kak_assert(StringView{vm.captures()[0]}  == \"bar\");\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{R\"(\\A\\w+)\"};\n        kak_assert(vm.exec(\"foo\\nbar\\nbaz\", RegexExecFlags::None));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]}  == \"foo\");\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{R\"(\\b\\w+\\z)\"};\n        kak_assert(vm.exec(\"foo\\nbar\\nbaz\", RegexExecFlags::None));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]}  == \"baz\");\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{R\"(a[^\\n]*\\n|\\n)\"};\n        kak_assert(vm.exec(\"foo\\nbar\\nb\", RegexExecFlags::None));\n        kak_assert(StringView{vm.captures()[0], vm.captures()[1]}  == \"ar\\n\");\n    }\n\n    {\n        TestVM<> vm{R\"(()*)\"};\n        kak_assert(not vm.exec(\" \"));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"(\\b(?<!-)(a|b|)(?!-)\\b)\"};\n        kak_assert(vm.exec(\"# foo bar\", RegexExecFlags::None));\n        kak_assert(*vm.captures()[0] == '#');\n    }\n\n    {\n        TestVM<> vm{R\"((?=))\"};\n        kak_assert(vm.exec(\"\"));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"((?i)FOO)\"};\n        kak_assert(vm.exec(\"foo\", RegexExecFlags::None));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"(.?(?=foo))\"};\n        kak_assert(vm.exec(\"afoo\", RegexExecFlags::None));\n        kak_assert(*vm.captures()[0] == 'a');\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"((?i)(?=Foo))\"};\n        kak_assert(vm.exec(\"fOO\", RegexExecFlags::None));\n        kak_assert(*vm.captures()[0] == 'f');\n    }\n\n    {\n        TestVM<> vm{R\"([d-ea-dcf-k]+)\"};\n        kak_assert(vm.exec(\"abcde\"));\n    }\n\n    {\n        TestVM<> vm{R\"((?i)[a-c]+)\"};\n        kak_assert(vm.exec(\"bCa\"));\n    }\n\n    {\n        TestVM<> vm{R\"([\\t-\\r]+)\"};\n        kak_assert(vm.exec(\"\\t\\n\\v\\f\\r\"));\n    }\n\n    {\n        TestVM<> vm{R\"([\\t-\\r]\\h+[\\t-\\r])\"};\n        kak_assert(vm.character_classes.size() == 1);\n        kak_assert(vm.exec(\"\\n  \\f\"));\n    }\n\n    {\n        TestVM<> vm{R\"([^\\x00-\\x7F]+)\"};\n        kak_assert(not vm.exec(\"ascii\"));\n        kak_assert(vm.exec(\"←↑→↓\"));\n        kak_assert(vm.exec(\"😄😊😉\"));\n    }\n\n    {\n        TestVM<> vm{R\"([^\\u000000-\\u00ffff]+)\"};\n        kak_assert(not vm.exec(\"ascii\"));\n        kak_assert(not vm.exec(\"←↑→↓\"));\n        kak_assert(vm.exec(\"😄😊😉\"));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{R\"(д)\"};\n        kak_assert(vm.exec(\"д\", RegexExecFlags::None));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{\"ab\"};\n        const char str[] = \"fa😄ab\";\n        kak_assert(not vm.exec(str, str+4, str, str + sizeof(str)-1, RegexExecFlags::None));\n    }\n\n    {\n        TestVM<> vm{R\"(\\0\\x0A\\u00260e\\u00260F)\"};\n        const char str[] = \"\\0\\n☎☏\"; // work around the null byte in the literal\n        kak_assert(vm.exec({str, str + sizeof(str)-1}));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{\".{40}\"};\n        kak_assert(vm.exec(\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", RegexExecFlags::None));\n    }\n\n    {\n        TestVM<RegexMode::Forward | RegexMode::Search> vm{\"(.{3,4}|f)oo\"};\n        kak_assert(vm.forward_start_desc and vm.forward_start_desc->offset == 4);\n        for (int c = 0; c < CompiledRegex::StartDesc::count; ++c)\n            kak_assert(vm.forward_start_desc->map[c] == (c == 'f' or c == 'o'));\n\n        kak_assert(vm.exec(\"xxxoo\", RegexExecFlags::None));\n        kak_assert(vm.exec(\"xfoo\", RegexExecFlags::None));\n        kak_assert(not vm.exec(\"😄xoo\", RegexExecFlags::None));\n    }\n\n    {\n        TestVM<RegexMode::Backward | RegexMode::Search> vm{\"oo(.{3,4}|f)\"};\n        kak_assert(vm.backward_start_desc and vm.backward_start_desc->offset == 4);\n        for (int c = 0; c < CompiledRegex::StartDesc::count; ++c)\n            kak_assert(vm.backward_start_desc->map[c] == (c == 'f' or c == 'o'));\n\n        kak_assert(vm.exec(\"ooxxx\", RegexExecFlags::None));\n        kak_assert(vm.exec(\"oofx\", RegexExecFlags::None));\n        kak_assert(not vm.exec(\"oox😄\", RegexExecFlags::None));\n    }\n\n    {\n        auto eq = [](const CompiledRegex::NamedCapture& lhs,\n                     const CompiledRegex::NamedCapture& rhs) {\n            return lhs.name == rhs.name and\n                   lhs.index == rhs.index;\n        };\n\n        TestVM<> vm{R\"((?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+))\"};\n        kak_assert(vm.exec(\"2019-01-03\", RegexExecFlags::None));\n        kak_assert(StringView{vm.captures()[2], vm.captures()[3]} == \"2019\");\n        kak_assert(StringView{vm.captures()[4], vm.captures()[5]} == \"01\");\n        kak_assert(StringView{vm.captures()[6], vm.captures()[7]} == \"03\");\n        kak_assert(vm.named_captures.size() == 3);\n        kak_assert(eq(vm.named_captures[0], {\"year\", 1}));\n        kak_assert(eq(vm.named_captures[1], {\"month\", 2}));\n        kak_assert(eq(vm.named_captures[2], {\"day\", 3}));\n    }\n\n    auto check_parse_error = [](StringView re, StringView expected_error) {\n        try\n        {\n            TestVM<>{re};\n            kak_assert(false);\n        }\n        catch (const regex_error& err)\n        {\n            kak_assert(err.what() == String{\"regex parse error: \"} + expected_error);\n        }\n    };\n\n    check_parse_error(\"(?=a*)\", \"Quantifiers cannot be used in lookarounds at '(?=a*)<<<HERE>>>'\");\n    check_parse_error(\"(?=(a))\", \"Lookaround can only contain literals, any chars or character classes at '(?=(a))<<<HERE>>>'\");\n    check_parse_error(\"(?=a|b)\", \"Alternations cannot be used in lookarounds at '(?=a|<<<HERE>>>b)'\");\n}};\n\n}\n"
  },
  {
    "path": "src/regex_vm.hh",
    "content": "#ifndef regex_vm_hh_INCLUDED\n#define regex_vm_hh_INCLUDED\n\n#include \"exception.hh\"\n#include \"flags.hh\"\n#include \"unicode.hh\"\n#include \"utf8.hh\"\n#include \"vector.hh\"\n#include \"utils.hh\"\n#include \"unique_ptr.hh\"\n\n#include <bit>\n#include <algorithm>\n\nnamespace Kakoune\n{\n\nstruct regex_error : runtime_error\n{\n    using runtime_error::runtime_error;\n};\n\nenum class CharacterType : unsigned char\n{\n    None                    = 0,\n    Whitespace              = 1 << 0,\n    HorizontalWhitespace    = 1 << 1,\n    Word                    = 1 << 2,\n    Digit                   = 1 << 3,\n    NotWhitespace           = 1 << 4,\n    NotHorizontalWhitespace = 1 << 5,\n    NotWord                 = 1 << 6,\n    NotDigit                = 1 << 7\n};\nconstexpr bool with_bit_ops(Meta::Type<CharacterType>) { return true; }\n\nbool is_ctype(CharacterType ctype, Codepoint cp);\n\nstruct CharacterClass\n{\n    struct Range\n    {\n        Codepoint min, max;\n        friend bool operator==(const Range&, const Range&) = default;\n    };\n\n    Vector<Range, MemoryDomain::Regex> ranges;\n    CharacterType ctypes = CharacterType::None;\n    bool negative = false;\n    bool ignore_case = false;\n\n    friend bool operator==(const CharacterClass&, const CharacterClass&) = default;\n\n    bool matches(Codepoint cp) const\n    {\n        if (ignore_case)\n            cp = to_lower(cp);\n\n        for (auto& [min, max] : ranges)\n        {\n            if (cp < min)\n                break;\n            else if (cp <= max)\n                return not negative;\n        }\n\n        return (ctypes != CharacterType::None and is_ctype(ctypes, cp)) != negative;\n    }\n\n};\n\nstruct CompiledRegex : UseMemoryDomain<MemoryDomain::Regex>\n{\n    enum Op : char\n    {\n        Match,\n        Literal,\n        AnyChar,\n        AnyCharExceptNewLine,\n        CharRange,\n        CharType,\n        CharClass,\n        Jump,\n        Split,\n        Save,\n        LineAssertion,\n        SubjectAssertion,\n        WordBoundary,\n        LookAround,\n    };\n\n    enum class Lookaround : Codepoint\n    {\n        OpBegin              = 0xF0000,\n        AnyChar              = 0xF0000,\n        AnyCharExceptNewLine = 0xF0001,\n        CharacterClass       = 0xF0002,\n        CharacterType        = 0xF8000,\n        OpEnd                = 0xFFFFF,\n        EndOfLookaround      = static_cast<Codepoint>(-1)\n    };\n\n    union Param\n    {\n        struct Literal\n        {\n            uint32_t codepoint : 24;\n            bool ignore_case : 1;\n        } literal;\n        struct CharRange\n        {\n            uint8_t min;\n            uint8_t max;\n            bool ignore_case : 1;\n            bool negative;\n        } range;\n        CharacterType character_type;\n        int16_t character_class_index;\n        int16_t jump_offset;\n        int16_t save_index;\n        struct Split\n        {\n            int16_t offset;\n            bool prioritize_parent : 1;\n        } split;\n        bool line_start;\n        bool subject_begin;\n        bool word_boundary_positive;\n        struct Lookaround\n        {\n            int16_t index;\n            bool ahead : 1;\n            bool positive : 1;\n            bool ignore_case : 1;\n        } lookaround;\n    };\n    static_assert(sizeof(Param) == 4);\n\n    struct Instruction\n    {\n        Op op;\n        mutable uint16_t last_step; // mutable as used during execution\n        Param param;\n    };\n#ifndef __ppc__\n    static_assert(sizeof(Instruction) == 8);\n#endif\n\n    explicit operator bool() const { return not instructions.empty(); }\n\n    struct NamedCapture\n    {\n        String name;\n        uint32_t index;\n    };\n\n    Vector<Instruction, MemoryDomain::Regex> instructions;\n    Vector<CharacterClass, MemoryDomain::Regex> character_classes;\n    Vector<Lookaround, MemoryDomain::Regex> lookarounds;\n    Vector<NamedCapture, MemoryDomain::Regex> named_captures;\n    uint32_t first_backward_inst; // -1 if no backward support, 0 if only backward, >0 if both forward and backward\n    uint32_t save_count;\n\n    struct StartDesc : UseMemoryDomain<MemoryDomain::Regex>\n    {\n        static constexpr Codepoint count = 256;\n        using OffsetLimits = std::numeric_limits<uint8_t>;\n        char start_byte = 0;\n        uint8_t offset = 0;\n        bool map[count];\n    };\n\n    UniquePtr<StartDesc> forward_start_desc;\n    UniquePtr<StartDesc> backward_start_desc;\n};\n\nString dump_regex(const CompiledRegex& program);\n\nenum class RegexCompileFlags\n{\n    None     = 0,\n    NoSubs   = 1 << 0,\n    Optimize = 1 << 1,\n    Backward = 1 << 2,\n    NoForward = 1 << 3,\n};\nconstexpr bool with_bit_ops(Meta::Type<RegexCompileFlags>) { return true; }\n\nCompiledRegex compile_regex(StringView re, RegexCompileFlags flags);\n\nenum class RegexExecFlags\n{\n    None              = 0,\n    NotBeginOfLine    = 1 << 1,\n    NotEndOfLine      = 1 << 2,\n    NotBeginOfWord    = 1 << 3,\n    NotEndOfWord      = 1 << 4,\n    NotInitialNull    = 1 << 5,\n};\n\nconstexpr bool with_bit_ops(Meta::Type<RegexExecFlags>) { return true; }\n\nenum class RegexMode\n{\n    Forward  = 1 << 0,\n    Backward = 1 << 1,\n    Search   = 1 << 2,\n    AnyMatch = 1 << 3,\n    NoSaves  = 1 << 4,\n};\nconstexpr bool with_bit_ops(Meta::Type<RegexMode>) { return true; }\nconstexpr bool has_direction(RegexMode mode)\n{\n    return (bool)(mode & RegexMode::Forward) xor\n           (bool)(mode & RegexMode::Backward);\n}\n\nconstexpr bool is_direction(RegexMode mode)\n{\n    return has_direction(mode) and\n           (mode & ~(RegexMode::Forward | RegexMode::Backward)) == RegexMode{0};\n}\n\ntemplate<typename It>\nstruct SentinelType { using Type = It; };\n\ntemplate<typename It>\n    requires requires { typename It::Sentinel; }\nstruct SentinelType<It> { using Type = typename It::Sentinel; };\n\ntemplate<typename Iterator, RegexMode mode>\n    requires (has_direction(mode))\nclass ThreadedRegexVM\n{\npublic:\n    ThreadedRegexVM(const CompiledRegex& program)\n      : m_program{program}\n    {\n        kak_assert((forward and program.first_backward_inst != 0) or\n                   (not forward and program.first_backward_inst != -1));\n    }\n\n    ThreadedRegexVM(ThreadedRegexVM&&) = default;\n    ThreadedRegexVM& operator=(ThreadedRegexVM&&) = default;\n    ThreadedRegexVM(const ThreadedRegexVM&) = delete;\n    ThreadedRegexVM& operator=(const ThreadedRegexVM&) = delete;\n\n    ~ThreadedRegexVM()\n    {\n        for (auto& saves : m_saves)\n        {\n            for (int i = m_program.save_count-1; i >= 0; --i)\n                saves.pos[i].~Iterator();\n            operator delete(saves.pos, m_program.save_count * sizeof(Iterator));\n        }\n    }\n\n    bool exec(const Iterator& begin, const Iterator& end,\n              const Iterator& subject_begin, const Iterator& subject_end,\n              RegexExecFlags flags)\n    {\n        return exec(begin, end, subject_begin, subject_end, flags, []{});\n    }\n\n    bool exec(const Iterator& begin, const Iterator& end,\n              const Iterator& subject_begin, const Iterator& subject_end,\n              RegexExecFlags flags, auto&& idle_func)\n    {\n        if (flags & RegexExecFlags::NotInitialNull and begin == end)\n            return false;\n\n        const ExecConfig config{\n            Sentinel{forward ? begin : end},\n            Sentinel{forward ? end : begin},\n            Sentinel{subject_begin},\n            Sentinel{subject_end},\n            flags\n        };\n\n        exec_program(forward ? begin : end, config, idle_func);\n\n        while (not m_threads.next_is_empty())\n            release_saves(m_threads.pop_next().saves);\n        return m_found_match;\n    }\n\n    ArrayView<const Iterator> captures() const\n    {\n        if (m_captures >= 0)\n        {\n            auto& saves = m_saves[m_captures];\n            for (int i = 0; i < m_program.save_count; ++i)\n            {\n                if ((saves.valid_mask & (1 << i)) == 0)\n                    saves.pos[i] = Iterator{};\n            }\n            return { saves.pos, m_program.save_count };\n        }\n        return {};\n    }\n\nprivate:\n    struct Saves\n    {\n        int32_t refcount;\n        union {\n            int32_t next_free;\n            uint32_t valid_mask;\n        };\n        Iterator* pos;\n    };\n\n    template<bool copy>\n    int16_t new_saves(Iterator* pos, uint32_t valid_mask)\n    {\n        kak_assert(not copy or pos != nullptr);\n        const auto count = m_program.save_count;\n        if (m_first_free >= 0)\n        {\n            const int16_t res = m_first_free;\n            Saves& saves = m_saves[res];\n            m_first_free = saves.next_free;\n            kak_assert(saves.refcount == 1);\n            if constexpr (copy)\n                std::copy_n(pos, std::bit_width(valid_mask), saves.pos);\n            saves.valid_mask = valid_mask;\n            return res;\n        }\n\n        auto* new_pos = reinterpret_cast<Iterator*>(operator new (count * sizeof(Iterator)));\n        for (size_t i = 0; i < count; ++i)\n            new (new_pos+i) Iterator{copy ? pos[i] : Iterator{}};\n        m_saves.push_back({1, {.valid_mask=valid_mask}, new_pos});\n        return static_cast<int16_t>(m_saves.size() - 1);\n    }\n\n    void release_saves(int16_t index)\n    {\n        if (index < 0)\n            return;\n        auto& saves = m_saves[index];\n        if (saves.refcount == 1)\n        {\n            saves.next_free = m_first_free;\n            m_first_free = index;\n        }\n        else\n            --saves.refcount;\n    };\n\n    struct Thread\n    {\n        const CompiledRegex::Instruction* inst;\n        int saves;\n    };\n\n    using StartDesc = CompiledRegex::StartDesc;\n    using Sentinel = typename SentinelType<Iterator>::Type;\n    struct ExecConfig\n    {\n        const Sentinel begin;\n        const Sentinel end;\n        const Sentinel subject_begin;\n        const Sentinel subject_end;\n        const RegexExecFlags flags;\n    };\n\n    // Steps a thread until it consumes the current character, matches or fail\n    [[gnu::always_inline]]\n    void step_current_thread(const Iterator& pos, Codepoint cp, uint16_t current_step, const ExecConfig& config)\n    {\n        Thread thread = m_threads.pop_current();\n        auto failed   = [&] { release_saves(thread.saves); };\n        auto consumed = [&] { m_threads.push_next(thread); };\n\n        while (true)\n        {\n            auto* inst = thread.inst++;\n            auto [op, last_step, param] = *inst;\n            // if this instruction was already executed for this step in another thread,\n            // then this thread is redundant and can be dropped\n            if (last_step == current_step)\n                return failed();\n            inst->last_step = current_step;\n\n            switch (op)\n            {\n                case CompiledRegex::Match:\n                    if ((pos != config.end and not (mode & RegexMode::Search)) or\n                        (config.flags & RegexExecFlags::NotInitialNull and pos == config.begin))\n                        return failed();\n\n                    release_saves(m_captures);\n                    m_captures = thread.saves;\n                    m_found_match = true;\n\n                    // remove lower priority threads\n                    while (not m_threads.current_is_empty())\n                        release_saves(m_threads.pop_current().saves);\n                    return;\n                case CompiledRegex::Literal:\n                    if (pos != config.end and\n                        param.literal.codepoint == (param.literal.ignore_case ? to_lower(cp) : cp))\n                        return consumed();\n                    return failed();\n                case CompiledRegex::AnyChar:\n                    return consumed();\n                case CompiledRegex::AnyCharExceptNewLine:\n                    if (pos != config.end and cp != '\\n')\n                        return consumed();\n                    return failed();\n                case CompiledRegex::CharRange:\n                    if (auto actual_cp = (param.range.ignore_case ? to_lower(cp) : cp);\n                        pos != config.end and\n                        (actual_cp >= param.range.min and actual_cp <= param.range.max) != param.range.negative)\n                        return consumed();\n                    return failed();\n                case CompiledRegex::CharType:\n                    if (pos != config.end and is_ctype(param.character_type, cp))\n                        return consumed();\n                    return failed();\n                case CompiledRegex::CharClass:\n                    if (pos != config.end and\n                        m_program.character_classes[param.character_class_index].matches(cp))\n                        return consumed();\n                    return failed();\n                case CompiledRegex::Jump:\n                    thread.inst = inst + param.jump_offset;\n                    break;\n                case CompiledRegex::Split:\n                    if (auto* target = inst + param.split.offset;\n                        target->last_step != current_step)\n                    {\n                        if (thread.saves >= 0)\n                            ++m_saves[thread.saves].refcount;\n                        if (not param.split.prioritize_parent)\n                            std::swap(thread.inst, target);\n                        m_threads.push_current({target, thread.saves});\n                    }\n                    break;\n                case CompiledRegex::Save:\n                    if constexpr (mode & RegexMode::NoSaves)\n                        break;\n                    if (thread.saves < 0)\n                        thread.saves = new_saves<false>(nullptr, 0);\n                    else if (auto& saves = m_saves[thread.saves]; saves.refcount > 1)\n                    {\n                        --saves.refcount;\n                        thread.saves = new_saves<true>(saves.pos, saves.valid_mask);\n                    }\n                    m_saves[thread.saves].pos[param.save_index] = pos;\n                    m_saves[thread.saves].valid_mask |= (1 << param.save_index);\n                    break;\n                case CompiledRegex::LineAssertion:\n                    if (not (param.line_start ? is_line_start(pos, config) : is_line_end(pos, config)))\n                        return failed();\n                    break;\n                case CompiledRegex::SubjectAssertion:\n                    if (pos != (param.subject_begin ? config.subject_begin : config.subject_end))\n                        return failed();\n                    break;\n                case CompiledRegex::WordBoundary:\n                    if (is_word_boundary(pos, config) != param.word_boundary_positive)\n                        return failed();\n                    break;\n                case CompiledRegex::LookAround:\n                    if (lookaround(param.lookaround, pos, config) != param.lookaround.positive)\n                        return failed();\n                    break;\n            }\n        }\n        return failed();\n    }\n\n    void exec_program(const Iterator& start, const ExecConfig& config, auto&& idle_func)\n    {\n        kak_assert(m_threads.current_is_empty() and m_threads.next_is_empty());\n        m_threads.ensure_initial_capacity();\n        release_saves(m_captures);\n        m_captures = -1;\n        m_found_match = false;\n\n        const auto* start_desc = (forward ? m_program.forward_start_desc : m_program.backward_start_desc).get();\n        Iterator next_start = start;\n        if (start_desc)\n        {\n            if constexpr (mode & RegexMode::Search)\n                next_start = find_next_start(start, config.end, *start_desc);\n            if (next_start == config.end or // Non null start_desc means we consume at least one char\n                (not (mode & RegexMode::Search) and\n                  not start_desc->map[static_cast<unsigned char>(forward ? *start : *std::prev(start))]))\n                return;\n        }\n\n        const auto insts = forward ? ArrayView(m_program.instructions).subrange(0, m_program.first_backward_inst)\n                                   : ArrayView(m_program.instructions).subrange(m_program.first_backward_inst);\n        m_threads.push_current({insts.begin(), -1});\n\n        uint16_t current_step = -1;\n        uint8_t idle_count = 0; // Run idle loop every 256 * 65536 == 16M codepoints\n        Iterator pos = next_start;\n        while (pos != config.end)\n        {\n            if (++current_step == 0)\n            {\n                if (++idle_count == 0)\n                    idle_func();\n\n                // We wrapped, avoid potential collision on inst.last_step by resetting them\n                for (auto& inst : insts)\n                    inst.last_step = 0;\n                current_step = 1; // step 0 is never valid\n            }\n\n            auto next = pos;\n            Codepoint cp = codepoint(next, config);\n\n            while (not m_threads.current_is_empty())\n                step_current_thread(pos, cp, current_step, config);\n\n            if ((mode & RegexMode::Search) and not m_found_match)\n            {\n                if (start_desc)\n                {\n                    if (pos == next_start)\n                        next_start = find_next_start(next, config.end, *start_desc);\n                    if (m_threads.next_is_empty())\n                        next = next_start;\n                }\n                if (not start_desc or next == next_start)\n                    m_threads.push_next({insts.begin(), -1});\n            }\n            else if (m_threads.next_is_empty() or (m_found_match and (mode & RegexMode::AnyMatch)))\n                return;\n\n            pos = next;\n            m_threads.swap_next();\n        }\n\n        if (++current_step == 0)\n        {\n            for (auto& inst : insts)\n                inst.last_step = 0;\n            current_step = 1; // step 0 is never valid\n        }\n        while (not m_threads.current_is_empty())\n            step_current_thread(pos, -1, current_step, config);\n    }\n\n    static Iterator find_next_start(const Iterator& start, const Sentinel& end, const StartDesc& start_desc)\n    {\n        auto pos = start;\n        if (char start_byte = start_desc.start_byte)\n        {\n            while (pos != end)\n            {\n                if constexpr (forward)\n                {\n                    if (*pos == start_byte)\n                       return utf8::advance(pos, start, -CharCount(start_desc.offset));\n                    ++pos;\n                }\n                else\n                {\n                    auto prev = utf8::previous(pos, end);\n                    if (*prev == start_byte)\n                       return utf8::advance(pos, start, CharCount(start_desc.offset));\n                    pos = prev;\n                }\n            }\n        }\n\n        while (pos != end)\n        {\n            static_assert(StartDesc::count <= 256, \"start desc should be ascii only\");\n            if constexpr (forward)\n            {\n                if (start_desc.map[static_cast<unsigned char>(*pos)])\n                    return utf8::advance(pos, start, -CharCount(start_desc.offset));\n                ++pos;\n            }\n            else\n            {\n                auto prev = utf8::previous(pos, end);\n                if (start_desc.map[static_cast<unsigned char>(*prev)])\n                    return utf8::advance(pos, start, CharCount(start_desc.offset));\n                pos = prev;\n            }\n        }\n        return pos;\n    }\n\n    bool lookaround(CompiledRegex::Param::Lookaround param, Iterator pos, const ExecConfig& config) const\n    {\n        using Lookaround = CompiledRegex::Lookaround;\n\n        if (not param.ahead)\n        {\n            if (pos == config.subject_begin)\n                return m_program.lookarounds[param.index] == Lookaround::EndOfLookaround;\n            utf8::to_previous(pos, config.subject_begin);\n        }\n\n        for (auto it = m_program.lookarounds.begin() + param.index; *it != Lookaround::EndOfLookaround; ++it)\n        {\n            if (param.ahead and pos == config.subject_end)\n                return false;\n\n            Codepoint cp = utf8::codepoint(pos, config.subject_end);\n            if (param.ignore_case)\n                cp = to_lower(cp);\n\n            const Lookaround op = *it;\n            if (op == Lookaround::AnyChar)\n            {} // any character matches\n            else if (op == Lookaround::AnyCharExceptNewLine)\n            {\n                if (cp == '\\n')\n                    return false;\n            }\n            else if (op >= Lookaround::CharacterClass and op < Lookaround::CharacterType)\n            {\n                auto index = to_underlying(op) - to_underlying(Lookaround::CharacterClass);\n                if (not m_program.character_classes[index].matches(cp))\n                    return false;\n            }\n            else if (op >= Lookaround::CharacterType and op < Lookaround::OpEnd)\n            {\n                auto ctype = static_cast<CharacterType>(to_underlying(op) & 0xFF);\n                if (not is_ctype(ctype, cp))\n                    return false;\n            }\n            else if (static_cast<Codepoint>(op) != cp)\n                return false;\n\n            if (not param.ahead and pos == config.subject_begin)\n                return *++it == Lookaround::EndOfLookaround;\n\n            param.ahead ? utf8::to_next(pos, config.subject_end)\n                        : utf8::to_previous(pos, config.subject_begin);\n        }\n        return true;\n    }\n\n    static bool is_line_start(const Iterator& pos, const ExecConfig& config)\n    {\n        if (pos == config.subject_begin)\n            return not (config.flags & RegexExecFlags::NotBeginOfLine);\n        return *(pos-1) == '\\n';\n    }\n\n    static bool is_line_end(const Iterator& pos, const ExecConfig& config)\n    {\n        if (pos == config.subject_end)\n            return not (config.flags & RegexExecFlags::NotEndOfLine);\n        return *pos == '\\n';\n    }\n\n    static bool is_word_boundary(const Iterator& pos, const ExecConfig& config)\n    {\n        if (pos == config.subject_begin)\n            return not (config.flags & RegexExecFlags::NotBeginOfWord);\n        if (pos == config.subject_end)\n            return not (config.flags & RegexExecFlags::NotEndOfWord);\n        return is_word(utf8::codepoint(utf8::previous(pos, config.subject_begin), config.subject_end)) !=\n               is_word(utf8::codepoint(pos, config.subject_end));\n    }\n\n    [[gnu::flatten]]\n    static Codepoint codepoint(Iterator& it, const ExecConfig& config)\n    {\n        if constexpr (forward)\n        {\n            return utf8::read_codepoint(it, config.end);\n        }\n        else\n        {\n            utf8::to_previous(it, config.end);\n            return utf8::codepoint(it, config.begin);\n        }\n    }\n\n    // This stores both the current thread stack and next thread stack in a ring buffer\n    // current threads go from m_current to m_next_begin (pushing decrements m_current)\n    // next thrads go from m_next_begin to m_next_end (pushing increments m_next_end)\n    struct DualThreadStack\n    {\n        bool current_is_empty() const { return m_current == m_next_begin; }\n        bool next_is_empty() const { return m_next_end == m_next_begin; }\n\n        [[gnu::always_inline]]\n        void push_current(Thread thread) { m_data[decrement(m_current)] = thread; grow_ifn(true); }\n        [[gnu::always_inline]]\n        Thread pop_current() { return m_data[post_increment(m_current)]; }\n\n        [[gnu::always_inline]]\n        void push_next(Thread thread) { m_data[post_increment(m_next_end)] = thread; grow_ifn(false); }\n        [[gnu::always_inline]]\n        Thread pop_next() { return m_data[decrement(m_next_end)]; }\n\n        void swap_next()\n        {\n            m_current = m_next_begin;\n            m_next_begin = m_next_end;\n        }\n\n        void ensure_initial_capacity()\n        {\n            if (m_capacity_mask != 0)\n                return;\n\n            constexpr uint32_t initial_capacity = 64 / sizeof(Thread);\n            static_assert(initial_capacity >= 4 and std::has_single_bit(initial_capacity));\n            m_data.reset(new Thread[initial_capacity]);\n            m_capacity_mask = initial_capacity-1;\n        }\n\n        [[gnu::always_inline]]\n        void grow_ifn(bool pushed_current)\n        {\n            if (m_current == m_next_end)\n                grow(pushed_current);\n        }\n\n        [[gnu::noinline]]\n        void grow(bool pushed_current)\n        {\n            auto capacity = m_capacity_mask + 1;\n            const auto new_capacity = capacity * 2;\n            Thread* new_data = new Thread[new_capacity];\n            Thread* old_data = m_data.get();\n            // copy the whole data to avoid having to consider either stack wrapping around\n            std::rotate_copy(old_data, old_data + m_current, old_data + capacity, new_data);\n            m_next_begin = (m_next_begin - m_current) & m_capacity_mask;\n            if (pushed_current and m_next_begin == 0)\n                m_next_begin = capacity;\n            m_next_end = capacity;\n            m_current = 0;\n\n            m_data.reset(new_data);\n            kak_assert(std::has_single_bit(new_capacity));\n            m_capacity_mask = new_capacity-1;\n        }\n\n    private:\n        uint32_t decrement(uint32_t& index)\n        {\n            index = (index - 1) & m_capacity_mask;\n            return index;\n        }\n\n        uint32_t post_increment(uint32_t& index)\n        {\n            auto res = index;\n            index = (index + 1) & m_capacity_mask;\n            return res;\n        }\n\n        UniquePtr<Thread[]> m_data;\n        uint32_t m_capacity_mask = 0; // Maximum capacity should be 2*instruction count, so 65536\n        uint32_t m_current = 0;\n        uint32_t m_next_begin = 0;\n        uint32_t m_next_end = 0;\n    };\n\n    static constexpr bool forward = mode & RegexMode::Forward;\n\n    const CompiledRegex& m_program;\n    DualThreadStack m_threads;\n    Vector<Saves, MemoryDomain::Regex> m_saves;\n    int16_t m_first_free = -1;\n    int16_t m_captures = -1;\n    bool m_found_match = false;\n};\n\n}\n\n#endif // regex_vm_hh_INCLUDED\n"
  },
  {
    "path": "src/register_manager.cc",
    "content": "#include \"register_manager.hh\"\n\n#include \"assert.hh\"\n#include \"context.hh\"\n#include \"hash_map.hh\"\n#include \"format.hh\"\n#include \"ranges.hh\"\n#include \"hook_manager.hh\"\n\nnamespace Kakoune\n{\n\nRegister::RestoreInfo Register::save(const Context& context)\n{\n    return get(context) | gather<RestoreInfo>();\n}\n\nvoid Register::restore(Context& context, const RestoreInfo& info)\n{\n    set(context, info, true);\n}\n\nvoid StaticRegister::set(Context& context, ConstArrayView<String> values, bool)\n{\n    m_content.assign(values.begin(), values.end());\n    if (not m_disable_modified_hook)\n        context.hooks().run_hook(Hook::RegisterModified, m_name, context);\n}\n\nConstArrayView<String> StaticRegister::get(const Context&)\n{\n    if (m_content.empty())\n        return ConstArrayView<String>(String::ms_empty);\n    else\n        return ConstArrayView<String>(m_content);\n}\n\nconst String& StaticRegister::get_main(const Context& context, size_t main_index)\n{\n    auto content = get(context);\n    return content[std::min(main_index, content.size() - 1)];\n}\n\nvoid HistoryRegister::set(Context& context, ConstArrayView<String> values, bool restoring)\n{\n    constexpr size_t size_limit = 1000;\n\n    if (restoring)\n    {\n        StaticRegister::set(context, values, true);\n        m_content.erase(remove_if(m_content, [](auto&& s) { return s.empty(); }), m_content.end());\n        return;\n    }\n\n    for (auto&& entry : values | reverse())\n    {\n        m_content.erase(std::remove(m_content.begin(), m_content.end(), entry), m_content.end());\n        m_content.insert(m_content.begin(), entry);\n    }\n\n    if (m_content.size() > size_limit)\n        m_content.erase(m_content.end() - (m_content.size() - size_limit), m_content.end());\n\n    if (not m_disable_modified_hook)\n        context.hooks().run_hook(Hook::RegisterModified, m_name, context);\n}\n\nconst String& HistoryRegister::get_main(const Context&, size_t)\n{\n    return m_content.empty() ? String::ms_empty : m_content.front();\n}\n\nstatic const HashMap<StringView, Codepoint> reg_names {\n    { \"slash\", '/' },\n    { \"dquote\", '\"' },\n    { \"pipe\", '|' },\n    { \"caret\", '^' },\n    { \"arobase\", '@' },\n    { \"percent\", '%' },\n    { \"dot\", '.' },\n    { \"hash\", '#' },\n    { \"underscore\", '_' },\n    { \"colon\", ':' }\n};\n\nRegister& RegisterManager::operator[](StringView reg) const\n{\n    if (reg.length() == 1)\n        return (*this)[reg[0_byte]];\n\n    auto it = reg_names.find(reg);\n    if (it == reg_names.end())\n        throw runtime_error(format(\"no such register: '{}'\", reg));\n    return (*this)[it->value];\n}\n\nRegister& RegisterManager::operator[](Codepoint c) const\n{\n    c = to_lower(c);\n    auto it = m_registers.find(c);\n    if (it == m_registers.end())\n        throw runtime_error(format(\"no such register: '{}'\", c));\n\n    return *(it->value);\n}\n\nvoid RegisterManager::add_register(Codepoint c, UniquePtr<Register> reg)\n{\n    auto& reg_ptr = m_registers[c];\n    kak_assert(not reg_ptr);\n    reg_ptr = std::move(reg);\n}\n\nCandidateList RegisterManager::complete_register_name(StringView prefix, ByteCount cursor_pos) const\n{\n    return complete(prefix, cursor_pos, reg_names | transform([](auto& i) { return i.key; }));\n}\n\n}\n"
  },
  {
    "path": "src/register_manager.hh",
    "content": "#ifndef register_manager_hh_INCLUDED\n#define register_manager_hh_INCLUDED\n\n#include \"array_view.hh\"\n#include \"completion.hh\"\n#include \"exception.hh\"\n#include \"utils.hh\"\n#include \"unique_ptr.hh\"\n#include \"hash_map.hh\"\n#include \"string.hh\"\n#include \"vector.hh\"\n\nnamespace Kakoune\n{\n\nclass Context;\n\nclass Register\n{\npublic:\n    virtual ~Register() = default;\n\n    virtual void set(Context& context, ConstArrayView<String> values, bool restoring = false) = 0;\n    virtual ConstArrayView<String> get(const Context& context) = 0;\n    virtual const String& get_main(const Context& context, size_t main_index) = 0;\n\n    using RestoreInfo = Vector<String, MemoryDomain::Registers>;\n    RestoreInfo save(const Context& context);\n    void restore(Context& context, const RestoreInfo& info);\n\n    NestedBool& modified_hook_disabled() { return m_disable_modified_hook; }\n\nprotected:\n    NestedBool m_disable_modified_hook;\n};\n\n// static value register, which can be modified\nclass StaticRegister : public Register\n{\npublic:\n    StaticRegister(String name) : m_name{std::move(name)} {}\n\n    void set(Context& context, ConstArrayView<String> values, bool) override;\n    ConstArrayView<String> get(const Context&) override;\n    const String& get_main(const Context& context, size_t main_index) override;\n\nprotected:\n    String m_name;\n    Vector<String, MemoryDomain::Registers> m_content;\n};\n\n// Dynamic value register, use it's RegisterRetriever\n// to get it's value when needed.\ntemplate<typename Getter, typename Setter>\nclass DynamicRegister : public StaticRegister\n{\npublic:\n    DynamicRegister(String name, Getter getter, Setter setter)\n        : StaticRegister(std::move(name)), m_getter(std::move(getter)), m_setter(std::move(setter)) {}\n\n    void set(Context& context, ConstArrayView<String> values, bool) override\n    {\n        m_setter(context, values);\n    }\n\n    ConstArrayView<String> get(const Context& context) override\n    {\n        m_content = m_getter(context);\n        return StaticRegister::get(context);\n    }\n\nprivate:\n    Getter m_getter;\n    Setter m_setter;\n};\n\n// Register that is used to store some kind prompt history\nclass HistoryRegister : public StaticRegister\n{\npublic:\n    using StaticRegister::StaticRegister;\n\n    void set(Context& context, ConstArrayView<String> values, bool restoring) override;\n    const String& get_main(const Context&, size_t) override;\n};\n\ntemplate<typename Func>\nUniquePtr<Register> make_dyn_reg(String name, Func func)\n{\n    auto setter = [](Context&, ConstArrayView<String>)\n    {\n        throw runtime_error(\"this register is not assignable\");\n    };\n    return make_unique_ptr<DynamicRegister<Func, decltype(setter)>>(name, std::move(func), setter);\n}\n\ntemplate<typename Getter, typename Setter>\nUniquePtr<Register> make_dyn_reg(String name, Getter getter, Setter setter)\n{\n    return make_unique_ptr<DynamicRegister<Getter, Setter>>(name, std::move(getter), std::move(setter));\n}\n\nclass NullRegister : public Register\n{\npublic:\n    void set(Context&, ConstArrayView<String>, bool) override {}\n\n    ConstArrayView<String> get(const Context&) override\n    {\n        return ConstArrayView<String>(String::ms_empty);\n    }\n\n    const String& get_main(const Context&, size_t) override\n    {\n        return String::ms_empty;\n    }\n};\n\nclass RegisterManager : public Singleton<RegisterManager>\n{\npublic:\n    Register& operator[](StringView reg) const;\n    Register& operator[](Codepoint c) const;\n    void add_register(Codepoint c, UniquePtr<Register> reg);\n    CandidateList complete_register_name(StringView prefix, ByteCount cursor_pos) const;\n\n    auto begin() const { return m_registers.begin(); }\n    auto end() const { return m_registers.end(); }\n\nprotected:\n    HashMap<Codepoint, UniquePtr<Register>, MemoryDomain::Registers> m_registers;\n};\n\n}\n\n#endif // register_manager_hh_INCLUDED\n"
  },
  {
    "path": "src/remote.cc",
    "content": "#include \"remote.hh\"\n\n#include \"buffer_utils.hh\"\n#include \"debug.hh\"\n#include \"client_manager.hh\"\n#include \"command_manager.hh\"\n#include \"display_buffer.hh\"\n#include \"event_manager.hh\"\n#include \"file.hh\"\n#include \"hash_map.hh\"\n#include \"optional.hh\"\n#include \"user_interface.hh\"\n#include \"ranges.hh\"\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <sys/wait.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <string.h>\n#include <pwd.h>\n#include <fcntl.h>\n#include <errno.h>\n\n\nnamespace Kakoune\n{\n\nenum class MessageType : uint8_t\n{\n    Unknown,\n    Connect,\n    Command,\n    MenuShow,\n    MenuSelect,\n    MenuHide,\n    InfoShow,\n    InfoHide,\n    Draw,\n    DrawStatus,\n    Refresh,\n    SetOptions,\n    Exit,\n    Key,\n    Paste,\n};\n\nclass MsgWriter\n{\npublic:\n    MsgWriter(RemoteBuffer& buffer, MessageType type)\n        : m_buffer{buffer}, m_start{(uint32_t)buffer.size()}\n    {\n        write_field(type);\n        write_field((uint32_t)0); // message size, to be patched on write\n    }\n\n    ~MsgWriter()\n    {\n        uint32_t count = (uint32_t)m_buffer.size() - m_start;\n        memcpy(m_buffer.data() + m_start + sizeof(MessageType), &count, sizeof(uint32_t));\n    }\n\n    template<typename ...Args>\n    void write(Args&&... args)\n    {\n        (write_field(std::forward<Args>(args)), ...);\n    }\n\nprivate:\n    void write_raw(const char* val, size_t size)\n    {\n        m_buffer.insert(m_buffer.end(), val, val + size);\n    }\n\n    template<typename T>\n    void write_field(const T& val)\n    {\n        static_assert(std::is_trivially_copyable<T>::value, \"\");\n        write_raw((const char*)&val, sizeof(val));\n    }\n\n    void write_field(StringView str)\n    {\n        write_field(str.length());\n        write_raw(str.data(), (int)str.length());\n    };\n\n    void write_field(const String& str)\n    {\n        write_field(StringView{str});\n    }\n\n    template<typename T>\n    void write_field(ConstArrayView<T> view)\n    {\n        write_field<uint32_t>(view.size());\n        for (auto& val : view)\n            write_field(val);\n    }\n\n    template<typename T, MemoryDomain domain>\n    void write_field(const Vector<T, domain>& vec)\n    {\n        write_field(ConstArrayView<T>(vec));\n    }\n\n    template<typename Key, typename Val, MemoryDomain domain>\n    void write_field(const HashMap<Key, Val, domain>& map)\n    {\n        write_field<uint32_t>(map.size());\n        for (auto& val : map)\n        {\n            write_field(val.key);\n            write_field(val.value);\n        }\n    }\n\n    template<typename T>\n    void write_field(const Optional<T>& val)\n    {\n        write_field((bool)val);\n        if (val)\n            write_field(*val);\n    }\n\n    void write_field(Color color)\n    {\n        write_field(color.color);\n        if (color.isRGB())\n        {\n            write_field(color.r);\n            write_field(color.g);\n            write_field(color.b);\n        }\n    }\n\n    void write_field(const DisplayAtom& atom)\n    {\n        write_field(atom.content());\n        write_field(atom.face);\n    }\n\n    void write_field(const DisplayLine& line)\n    {\n        write_field(line.atoms());\n    }\n\n    void write_field(const DisplayBuffer& display_buffer)\n    {\n        write_field(display_buffer.lines());\n    }\n\nprivate:\n    RemoteBuffer& m_buffer;\n    uint32_t m_start;\n};\n\nclass MsgReader\n{\nprivate:\n    template<typename T>\n    struct Reader {\n        static T read(MsgReader& reader)\n        {\n            static_assert(std::is_trivially_copyable<T>::value, \"\");\n            T res;\n            reader.read(reinterpret_cast<char*>(&res), sizeof(T));\n            return res;\n        }\n    };\n\n    template<typename T, MemoryDomain domain>\n    struct Reader<Vector<T,domain>> {\n        static Vector<T, domain> read(MsgReader& reader)\n        {\n            uint32_t size = Reader<uint32_t>::read(reader);\n            Vector<T,domain> res;\n            res.reserve(size);\n            while (size--)\n                res.push_back(std::move(Reader<T>::read(reader)));\n            return res;\n        }\n    };\n\n    template<typename T>\n    struct Reader<ArrayView<T>> : Reader<Vector<std::remove_cv_t<T>, MemoryDomain::Undefined>> {};\n\n    template<typename Key, typename Value, MemoryDomain domain>\n    struct Reader<HashMap<Key, Value, domain>> {\n        static HashMap<Key, Value, domain> read(MsgReader& reader)\n        {\n            uint32_t size = Reader<uint32_t>::read(reader);\n            HashMap<Key, Value, domain> res;\n            res.reserve(size);\n            while (size--)\n            {\n                auto key = Reader<Key>::read(reader);\n                auto val = Reader<Value>::read(reader);\n                res.insert({std::move(key), std::move(val)});\n            }\n            return res;\n        }\n    };\n\n    template<typename T>\n    struct Reader<Optional<T>> {\n        static Optional<T> read(MsgReader& reader)\n        {\n            if (not Reader<bool>::read(reader))\n                return {};\n            return Reader<T>::read(reader);\n        }\n    };\n\npublic:\n    void read_available(int sock)\n    {\n        if (m_write_pos < header_size)\n        {\n            m_stream.resize(header_size);\n            read_from_socket(sock, header_size - m_write_pos);\n            if (m_write_pos == header_size)\n            {\n                if (size() < header_size)\n                    throw disconnected{\"invalid message received\"};\n                m_stream.resize(size());\n            }\n        }\n        else\n            read_from_socket(sock, size() - m_write_pos);\n    }\n\n    bool ready() const\n    {\n        return m_write_pos >= header_size and m_write_pos == size();\n    }\n\n    uint32_t size() const\n    {\n        kak_assert(m_write_pos >= header_size);\n        uint32_t res;\n        memcpy(&res, m_stream.data() + sizeof(MessageType), sizeof(uint32_t));\n        return res;\n    }\n\n    MessageType type() const\n    {\n        kak_assert(m_write_pos >= header_size);\n        return *reinterpret_cast<const MessageType*>(m_stream.data());\n    }\n\n    void read(char* buffer, size_t size)\n    {\n        if (m_read_pos + size > m_stream.size())\n            throw disconnected{\"tried to read after message end\"};\n        memcpy(buffer, m_stream.data() + m_read_pos, size);\n        m_read_pos += size;\n    }\n\n    template<typename T>\n    auto read()\n    {\n        return Reader<T>::read(*this);\n    }\n\n    Optional<int> ancillary_fd()\n    {\n        auto res = m_ancillary_fd;\n        m_ancillary_fd.reset();\n        return res;\n    }\n\n    ~MsgReader()\n    {\n        m_ancillary_fd.map(close);\n    }\n\n    void reset()\n    {\n        m_stream.resize(0);\n        m_write_pos = 0;\n        m_read_pos = header_size;\n        m_ancillary_fd.map(close);\n    }\n\nprivate:\n    void read_from_socket(int sock, size_t size)\n    {\n        kak_assert(m_write_pos + size <= m_stream.size());\n        iovec io{m_stream.data() + m_write_pos, size};\n        alignas(cmsghdr) char fdbuf[CMSG_SPACE(sizeof(int))];\n\n        msghdr msg{};\n        msg.msg_iov = &io;\n        msg.msg_iovlen = 1;\n        msg.msg_control = fdbuf;\n        msg.msg_controllen = sizeof(fdbuf);\n\n        int res = recvmsg(sock, &msg, 0);\n        if (res <= 0)\n            throw disconnected{format(\"socket read failed: {}\", strerror(errno))};\n\n        m_write_pos += res;\n\n        if (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);\n            cmsg && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS && cmsg->cmsg_len == CMSG_LEN(sizeof(int)))\n        {\n            m_ancillary_fd.map(close);\n            memcpy(&m_ancillary_fd.emplace(), CMSG_DATA(cmsg), sizeof(int));\n            fcntl(*m_ancillary_fd, F_SETFD, FD_CLOEXEC);\n        }\n    }\n\n    static constexpr uint32_t header_size = sizeof(MessageType) + sizeof(uint32_t);\n    Vector<char, MemoryDomain::Remote> m_stream;\n    Optional<int> m_ancillary_fd;\n    uint32_t m_write_pos = 0;\n    uint32_t m_read_pos = header_size;\n};\n\ntemplate<>\nstruct MsgReader::Reader<String> {\n    static String read(MsgReader& reader)\n    {\n        ByteCount length = Reader<ByteCount>::read(reader);\n        String res;\n        if (length > 0)\n        {\n            res.force_size((int)length);\n            reader.read(&res[0_byte], (int)length);\n        }\n        return res;\n    }\n};\n\ntemplate<>\nstruct MsgReader::Reader<Color> {\n    static Color read(MsgReader& reader)\n    {\n        Color res;\n        res.color = Reader<Color::NamedColor>::read(reader);\n        if (res.isRGB())\n        {\n            res.r = Reader<unsigned char>::read(reader);\n            res.g = Reader<unsigned char>::read(reader);\n            res.b = Reader<unsigned char>::read(reader);\n        }\n        return res;\n    }\n};\n\ntemplate<>\nstruct MsgReader::Reader<DisplayAtom> {\n    static DisplayAtom read(MsgReader& reader)\n    {\n        String content = Reader<String>::read(reader);\n        return {std::move(content), Reader<Face>::read(reader)};\n    }\n};\n\ntemplate<>\nstruct MsgReader::Reader<DisplayLine> {\n    static DisplayLine read(MsgReader& reader)\n    {\n        return {Reader<Vector<DisplayAtom>>::read(reader)};\n    }\n};\n\ntemplate<>\nstruct MsgReader::Reader<DisplayBuffer> {\n    static DisplayBuffer read(MsgReader& reader)\n    {\n        DisplayBuffer db;\n        db.lines() = Reader<Vector<DisplayLine>>::read(reader);\n        return db;\n    }\n};\n\n\nclass RemoteUI : public UserInterface\n{\npublic:\n    RemoteUI(int socket, DisplayCoord dimensions);\n    ~RemoteUI() override;\n\n    bool is_ok() const override { return m_socket_watcher.fd() != -1; }\n    void menu_show(ConstArrayView<DisplayLine> choices,\n                   DisplayCoord anchor, Face fg, Face bg,\n                   MenuStyle style) override;\n    void menu_select(int selected) override;\n    void menu_hide() override;\n\n    void info_show(const DisplayLine& title, const DisplayLineList& content,\n                   DisplayCoord anchor, Face face,\n                   InfoStyle style) override;\n    void info_hide() override;\n\n    void draw(const DisplayBuffer& display_buffer,\n              DisplayCoord cursor_pos,\n              const Face& default_face,\n              const Face& padding_face,\n              ColumnCount widget_columns) override;\n\n    void draw_status(const DisplayLine& prompt,\n                     const DisplayLine& content,\n                     const ColumnCount cursor_pos,\n                     const DisplayLine& mode_line,\n                     const Face& default_face) override;\n\n    void refresh(bool force) override;\n\n    DisplayCoord dimensions() override { return m_dimensions; }\n\n    void set_on_key(OnKeyCallback callback) override\n    { m_on_key = std::move(callback); }\n\n    void set_on_paste(OnPasteCallback callback) override\n    { m_on_paste = std::move(callback); }\n\n    void set_ui_options(const Options& options) override;\n\n    void exit(int status);\n\nprivate:\n    template<typename ...Args>\n    void send_message(MessageType type, Args&&... args)\n    {\n        MsgWriter msg{m_send_buffer, type};\n        msg.write(std::forward<Args>(args)...);\n        m_socket_watcher.events() |= FdEvents::Write;\n    }\n\n    FDWatcher     m_socket_watcher;\n    MsgReader     m_reader;\n    DisplayCoord  m_dimensions;\n    OnKeyCallback m_on_key;\n    OnPasteCallback m_on_paste;\n    RemoteBuffer  m_send_buffer;\n};\n\nstatic bool send_data(int fd, RemoteBuffer& buffer, Optional<int> ancillary_fd = {})\n{\n    while (not buffer.empty() and fd_writable(fd))\n    {\n        iovec io{buffer.data(), buffer.size()};\n        alignas(cmsghdr) char fdbuf[CMSG_SPACE(sizeof(int))];\n\n        msghdr msg{};\n        msg.msg_iov = &io;\n        msg.msg_iovlen = 1;\n        if (ancillary_fd)\n        {\n            msg.msg_control = fdbuf;\n            msg.msg_controllen = sizeof(fdbuf);\n\n            cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);\n            cmsg->cmsg_len = CMSG_LEN(sizeof(int));\n            cmsg->cmsg_level = SOL_SOCKET;\n            cmsg->cmsg_type = SCM_RIGHTS;\n            memcpy(CMSG_DATA(cmsg), &*ancillary_fd, sizeof(int));\n        }\n\n        int res = sendmsg(fd, &msg, 0);\n        if (res <= 0)\n              throw disconnected{format(\"socket write failed: {}\", strerror(errno))};\n         buffer.erase(buffer.begin(), buffer.begin() + res);\n    }\n    return buffer.empty();\n}\n\nRemoteUI::RemoteUI(int socket, DisplayCoord dimensions)\n    : m_socket_watcher(socket,  FdEvents::Read | FdEvents::Write, EventMode::Urgent,\n                       [this](FDWatcher& watcher, FdEvents events, EventMode) {\n          const int sock = watcher.fd();\n          try\n          {\n              if (events & FdEvents::Write and send_data(sock, m_send_buffer))\n                  m_socket_watcher.events() &= ~FdEvents::Write;\n\n              while (events & FdEvents::Read and fd_readable(sock))\n              {\n                  m_reader.read_available(sock);\n\n                  if (not m_reader.ready())\n                      continue;\n\n                   if (m_reader.type() == MessageType::Key)\n                   {\n                       auto key = m_reader.read<Key>();\n                       m_reader.reset();\n                       if (key.modifiers == Key::Modifiers::Resize)\n                           m_dimensions = key.coord();\n                       m_on_key(key);\n                   }\n                   else if (m_reader.type() == MessageType::Paste)\n                   {\n                       auto content = m_reader.read<String>();\n                       m_reader.reset();\n                       m_on_paste(content);\n                   }\n                   else\n                   {\n                       m_socket_watcher.close_fd();\n                       return;\n                   }\n              }\n          }\n          catch (const disconnected& err)\n          {\n              write_to_debug_buffer(format(\"Error while transfering remote messages: {}\", err.what()));\n              m_socket_watcher.close_fd();\n          }\n      }),\n      m_dimensions(dimensions)\n{\n    write_to_debug_buffer(format(\"remote client connected: {}\", m_socket_watcher.fd()));\n}\n\nRemoteUI::~RemoteUI()\n{\n    // Try to send the remaining data if possible, as it might contain the desired exit status\n    try\n    {\n        if (m_socket_watcher.fd() != -1)\n            send_data(m_socket_watcher.fd(), m_send_buffer);\n    }\n    catch (disconnected&)\n    {\n    }\n\n    write_to_debug_buffer(format(\"remote client disconnected: {}\", m_socket_watcher.fd()));\n    m_socket_watcher.close_fd();\n}\n\nvoid RemoteUI::menu_show(ConstArrayView<DisplayLine> choices,\n                         DisplayCoord anchor, Face fg, Face bg,\n                         MenuStyle style)\n{\n    send_message(MessageType::MenuShow, choices, anchor, fg, bg, style);\n}\n\nvoid RemoteUI::menu_select(int selected)\n{\n    send_message(MessageType::MenuSelect, selected);\n}\n\nvoid RemoteUI::menu_hide()\n{\n    send_message(MessageType::MenuHide);\n}\n\nvoid RemoteUI::info_show(const DisplayLine& title, const DisplayLineList& content,\n                         DisplayCoord anchor, Face face,\n                         InfoStyle style)\n{\n    send_message(MessageType::InfoShow, title, content, anchor, face, style);\n}\n\nvoid RemoteUI::info_hide()\n{\n    send_message(MessageType::InfoHide);\n}\n\nvoid RemoteUI::draw(const DisplayBuffer& display_buffer,\n                    DisplayCoord cursor_pos,\n                    const Face& default_face,\n                    const Face& padding_face,\n                    ColumnCount widget_columns)\n{\n    send_message(MessageType::Draw, display_buffer, cursor_pos, default_face, padding_face, widget_columns);\n}\n\nvoid RemoteUI::draw_status(const DisplayLine& prompt,\n                           const DisplayLine& content,\n                           const ColumnCount cursor_pos,\n                           const DisplayLine& mode_line,\n                           const Face& default_face)\n{\n    send_message(MessageType::DrawStatus, prompt, content, cursor_pos, mode_line, default_face);\n}\n\nvoid RemoteUI::refresh(bool force)\n{\n    send_message(MessageType::Refresh, force);\n}\n\nvoid RemoteUI::set_ui_options(const Options& options)\n{\n    send_message(MessageType::SetOptions, options);\n}\n\nvoid RemoteUI::exit(int status)\n{\n    send_message(MessageType::Exit, status);\n}\n\nString get_user_name()\n{\n    auto pw = getpwuid(geteuid());\n    if (pw)\n      return pw->pw_name;\n    return getenv(\"USER\");\n}\n\nconst String& session_directory()\n{\n    static String session_dir = [] {\n        StringView xdg_runtime_dir = getenv(\"XDG_RUNTIME_DIR\");\n        if (not xdg_runtime_dir.empty())\n        {\n            if (struct stat st; stat(xdg_runtime_dir.zstr(), &st) == 0 && st.st_uid == geteuid())\n                return format(\"{}/kakoune\", xdg_runtime_dir);\n            else\n                write_to_debug_buffer(\"XDG_RUNTIME_DIR does not exist or not owned by current user, using tmpdir\");\n        }\n        return format(\"{}/kakoune-{}\", tmpdir(), get_user_name());\n    }();\n    return session_dir;\n}\n\nString session_path(StringView session, bool assume_valid)\n{\n    if (not assume_valid and not all_of(session, is_identifier))\n        throw runtime_error{format(\"invalid session name: '{}'\", session)};\n    String path = format(\"{}/{}\", session_directory(), session);\n    if (not assume_valid and path.length() + 1 > sizeof sockaddr_un{}.sun_path)\n        throw runtime_error{format(\"socket path too long: '{}'\", path)};\n    return path;\n}\n\nstatic sockaddr_un session_addr(StringView session)\n{\n    sockaddr_un addr;\n    addr.sun_family = AF_UNIX;\n    strcpy(addr.sun_path, session_path(session).c_str());\n    return addr;\n}\n\nstatic int connect_to(StringView session)\n{\n    int sock = socket(AF_UNIX, SOCK_STREAM, 0);\n    fcntl(sock, F_SETFD, FD_CLOEXEC);\n    sockaddr_un addr = session_addr(session);\n    if (connect(sock, (sockaddr*)&addr, sizeof(addr.sun_path)) == -1)\n        throw disconnected(format(\"connect to {} failed\", addr.sun_path));\n    return sock;\n}\n\nbool check_session(StringView session)\n{\n    int sock = socket(AF_UNIX, SOCK_STREAM, 0);\n    auto close_sock = OnScopeEnd([sock]{ close(sock); });\n    sockaddr_un addr = session_addr(session);\n    return connect(sock, (sockaddr*)&addr, sizeof(addr.sun_path)) != -1;\n}\n\nRemoteClient::RemoteClient(StringView session, StringView name, UniquePtr<UserInterface>&& ui,\n                           int pid, const EnvVarMap& env_vars, StringView init_command,\n                           Optional<BufferCoord> init_coord, Optional<int> stdin_fd)\n    : m_ui(std::move(ui))\n{\n    int sock = connect_to(session);\n\n    {\n        MsgWriter msg{m_send_buffer, MessageType::Connect};\n        msg.write(pid, name, init_command, init_coord, m_ui->dimensions(), env_vars);\n    }\n    send_data(sock, m_send_buffer, stdin_fd);\n\n    m_ui->set_on_key([this](Key key){\n        MsgWriter msg(m_send_buffer, MessageType::Key);\n        msg.write(key);\n        m_socket_watcher->events() |= FdEvents::Write;\n     });\n    m_ui->set_on_paste([this](StringView content){\n        MsgWriter msg(m_send_buffer, MessageType::Paste);\n        msg.write(content);\n        m_socket_watcher->events() |= FdEvents::Write;\n     });\n\n    m_socket_watcher.reset(new FDWatcher{sock, FdEvents::Read | FdEvents::Write, EventMode::Urgent,\n                           [this, reader = MsgReader{}](FDWatcher& watcher, FdEvents events, EventMode) mutable {\n        const int sock = watcher.fd();\n        if (events & FdEvents::Write and send_data(sock, m_send_buffer))\n            watcher.events() &= ~FdEvents::Write;\n\n        auto exec = [&]<typename ...Args>(void (UserInterface::*method)(Args...)) {\n            struct Impl // Use a constructor to ensure left-to-right parameter evaluation\n            {\n                Impl(UserInterface& ui, void (UserInterface::*method)(Args...), Args... args)\n                {\n                    (ui.*method)(std::forward<Args>(args)...);\n                }\n            };\n            Impl{*m_ui, method, reader.read<std::remove_cvref_t<Args>>()...};\n        };\n\n        while (events & FdEvents::Read and\n               not reader.ready() and fd_readable(sock))\n        {\n            reader.read_available(sock);\n\n            if (not reader.ready())\n                continue;\n\n            auto clear_reader = OnScopeEnd([&reader] { reader.reset(); });\n            switch (reader.type())\n            {\n            case MessageType::MenuShow:\n                exec(&UserInterface::menu_show);\n                break;\n            case MessageType::MenuSelect:\n                exec(&UserInterface::menu_select);\n                break;\n            case MessageType::MenuHide:\n                exec(&UserInterface::menu_hide);\n                break;\n            case MessageType::InfoShow:\n                exec(&UserInterface::info_show);\n                break;\n            case MessageType::InfoHide:\n                exec(&UserInterface::info_hide);\n                break;\n            case MessageType::Draw:\n                exec(&UserInterface::draw);\n                break;\n            case MessageType::DrawStatus:\n                exec(&UserInterface::draw_status);\n                break;\n            case MessageType::Refresh:\n                exec(&UserInterface::refresh);\n                break;\n            case MessageType::SetOptions:\n                exec(&UserInterface::set_ui_options);\n                break;\n            case MessageType::Exit:\n                m_exit_status = reader.read<int>();\n                watcher.close_fd();\n                return;\n            default:\n                kak_assert(false);\n            }\n        }\n    }});\n}\n\nbool RemoteClient::is_ui_ok() const\n{\n    return m_ui->is_ok();\n}\n\nvoid send_command(StringView session, StringView command)\n{\n    int sock = connect_to(session);\n    auto close_sock = OnScopeEnd([sock]{ close(sock); });\n    RemoteBuffer buffer;\n    {\n        MsgWriter msg{buffer, MessageType::Command};\n        msg.write(command);\n    }\n    write(sock, {buffer.data(), buffer.data() + buffer.size()});\n}\n\n\n// A client accepter handle a connection until it closes or a nul byte is\n// recieved. Everything recieved before is considered to be a command.\n//\n// * When a nul byte is recieved, the socket is handed to a new Client along\n//   with the command.\n// * When the connection is closed, the command is run in an empty context.\nclass Server::Accepter\n{\npublic:\n    Accepter(int socket)\n        : m_socket_watcher(socket, FdEvents::Read, EventMode::Normal,\n                           [this](FDWatcher&, FdEvents, EventMode mode) {\n                               handle_available_input(mode);\n                           })\n    {}\n\nprivate:\n    void handle_available_input(EventMode mode)\n    {\n        const int sock = m_socket_watcher.fd();\n        try\n        {\n            while (not m_reader.ready() and fd_readable(sock))\n                m_reader.read_available(sock);\n\n            if (mode != EventMode::Normal or not m_reader.ready())\n                return;\n\n            switch (m_reader.type())\n            {\n            case MessageType::Connect:\n            {\n                auto pid = m_reader.read<int>();\n                auto name = m_reader.read<String>();\n                auto init_cmds = m_reader.read<String>();\n                auto init_coord = m_reader.read<Optional<BufferCoord>>();\n                auto dimensions = m_reader.read<DisplayCoord>();\n                auto env_vars = m_reader.read<HashMap<String, String, MemoryDomain::EnvVars>>();\n\n                if (auto stdin_fd = m_reader.ancillary_fd())\n                    create_fifo_buffer(generate_buffer_name(\"*stdin-{}*\"), *stdin_fd, Buffer::Flags::None,\n                                       AutoScroll::NotInitially);\n                auto* ui = new RemoteUI{sock, dimensions};\n                ClientManager::instance().create_client(\n                    UniquePtr<UserInterface>(ui), pid, std::move(name),\n                    std::move(env_vars), init_cmds, {}, init_coord,\n                    [ui](int status) { ui->exit(status); });\n\n                Server::instance().remove_accepter(this);\n                break;\n            }\n            case MessageType::Command:\n            {\n                auto command = m_reader.read<String>();\n                if (not command.empty()) try\n                {\n                    Context context{Context::EmptyContextFlag{}};\n                    CommandManager::instance().execute(command, context);\n                }\n                catch (const runtime_error& e)\n                {\n                    write_to_debug_buffer(format(\"error running command '{}': {}\",\n                                                 command, e.what()));\n                }\n                close(sock);\n                Server::instance().remove_accepter(this);\n                break;\n            }\n            default:\n                write_to_debug_buffer(\"invalid introduction message received\");\n                close(sock);\n                Server::instance().remove_accepter(this);\n            }\n        }\n        catch (const disconnected& err)\n        {\n            write_to_debug_buffer(format(\"accepting connection failed: {}\", err.what()));\n            close(sock);\n            Server::instance().remove_accepter(this);\n        }\n    }\n\n    FDWatcher m_socket_watcher;\n    MsgReader m_reader;\n};\n\nServer::Server(String session_name, bool is_daemon)\n    : m_session{std::move(session_name)}, m_is_daemon{is_daemon}\n{\n    int listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);\n    fcntl(listen_sock, F_SETFD, FD_CLOEXEC);\n    sockaddr_un addr = session_addr(m_session);\n\n    make_directory(session_directory(), 0711);\n\n    // Do not give any access to the socket to other users by default\n    auto old_mask = umask(0077);\n    auto restore_mask = OnScopeEnd([old_mask]() { umask(old_mask); });\n\n    if (bind(listen_sock, (sockaddr*) &addr, sizeof(sockaddr_un)) == -1)\n       throw runtime_error(format(\"unable to bind listen socket '{}': {}\",\n                                  addr.sun_path, strerror(errno)));\n\n    if (listen(listen_sock, 4) == -1)\n       throw runtime_error(format(\"unable to listen on socket '{}': {}\",\n                                  addr.sun_path, strerror(errno)));\n\n    auto accepter = [this](FDWatcher& watcher, FdEvents, EventMode) {\n        sockaddr_un client_addr;\n        socklen_t   client_addr_len = sizeof(sockaddr_un);\n        int sock = accept(watcher.fd(), (sockaddr*) &client_addr,\n                          &client_addr_len);\n        if (sock == -1)\n            throw runtime_error(\"accept failed\");\n        fcntl(sock, F_SETFD, FD_CLOEXEC);\n\n        m_accepters.emplace_back(new Accepter{sock});\n    };\n    m_listener.reset(new FDWatcher{listen_sock, FdEvents::Read, EventMode::Urgent, accepter});\n}\n\nbool Server::rename_session(StringView name)\n{\n    String old_socket_file = session_path(m_session, true);\n    String new_socket_file = session_path(name);\n\n    if (file_exists(new_socket_file))\n        return false;\n\n    if (rename(old_socket_file.c_str(), new_socket_file.c_str()) != 0)\n        return false;\n\n    m_session = name.str();\n    return true;\n}\n\nvoid Server::close_session(bool do_unlink)\n{\n    if (do_unlink)\n    {\n        String socket_file = session_path(m_session, true);\n        unlink(socket_file.c_str());\n    }\n    m_listener->close_fd();\n    m_listener.reset();\n}\n\nServer::~Server()\n{\n    if (m_listener)\n        close_session();\n}\n\nvoid Server::remove_accepter(Accepter* accepter)\n{\n    auto it = find(m_accepters, accepter);\n    kak_assert(it != m_accepters.end());\n    m_accepters.erase(it);\n}\n\n}\n"
  },
  {
    "path": "src/remote.hh",
    "content": "#ifndef remote_hh_INCLUDED\n#define remote_hh_INCLUDED\n\n#include \"env_vars.hh\"\n#include \"exception.hh\"\n#include \"utils.hh\"\n#include \"vector.hh\"\n#include \"optional.hh\"\n#include \"unique_ptr.hh\"\n\nnamespace Kakoune\n{\n\nstruct disconnected : runtime_error\n{\n    using runtime_error::runtime_error;\n};\n\nclass FDWatcher;\nclass UserInterface;\n\ntemplate<typename T> struct Optional;\nstruct BufferCoord;\n\nusing RemoteBuffer = Vector<char, MemoryDomain::Remote>;\n\n// A remote client handle communication between a client running on the server\n// and a user interface running on the local process.\nclass RemoteClient\n{\npublic:\n    RemoteClient(StringView session, StringView name, UniquePtr<UserInterface>&& ui,\n                 int pid, const EnvVarMap& env_vars, StringView init_command,\n                 Optional<BufferCoord> init_coord, Optional<int> stdin_fd);\n\n    bool is_ui_ok() const;\n    const Optional<int>& exit_status() const { return m_exit_status; }\nprivate:\n    UniquePtr<UserInterface> m_ui;\n    UniquePtr<FDWatcher>     m_socket_watcher;\n    RemoteBuffer                   m_send_buffer;\n    Optional<int>                  m_exit_status;\n};\n\nvoid send_command(StringView session, StringView command);\nString get_user_name();\nconst String& session_directory();\nString session_path(StringView session, bool assume_valid = false);\n\nstruct Server : public Singleton<Server>\n{\n    Server(String session_name, bool daemon);\n    ~Server();\n    const String& session() const { return m_session; }\n\n    bool rename_session(StringView name);\n    void close_session(bool do_unlink = true);\n\n    bool negotiating() const { return not m_accepters.empty(); }\n\n    void daemonize() { m_is_daemon = true; }\n    bool is_daemon() const { return m_is_daemon; }\n\nprivate:\n    class Accepter;\n    void remove_accepter(Accepter* accepter);\n\n    String m_session;\n    bool m_is_daemon;\n    UniquePtr<FDWatcher> m_listener;\n    Vector<UniquePtr<Accepter>, MemoryDomain::Remote> m_accepters;\n};\n\nbool check_session(StringView session);\n\n}\n\n#endif // remote_hh_INCLUDED\n"
  },
  {
    "path": "src/safe_ptr.hh",
    "content": "#ifndef safe_ptr_hh_INCLUDED\n#define safe_ptr_hh_INCLUDED\n\n// #define SAFE_PTR_TRACK_CALLSTACKS\n\n#include \"assert.hh\"\n#include \"ref_ptr.hh\"\n\n#ifdef SAFE_PTR_TRACK_CALLSTACKS\n#include \"backtrace.hh\"\n#include \"vector.hh\"\n#include <algorithm>\n#endif\n\nnamespace Kakoune\n{\n\n// *** SafePtr: objects that assert nobody references them when they die ***\n\nclass SafeCountable\n{\npublic:\n#ifdef KAK_DEBUG\n    SafeCountable() {}\n    ~SafeCountable()\n    {\n        kak_assert(m_count == 0);\n        #ifdef SAFE_PTR_TRACK_CALLSTACKS\n        kak_assert(m_callstacks.empty());\n        #endif\n    }\n\n    SafeCountable(const SafeCountable&) {}\n    SafeCountable(SafeCountable&&) {}\n\n    SafeCountable& operator=(const SafeCountable& other) { return *this; }\n    SafeCountable& operator=(SafeCountable&& other) { return *this; }\n\nprivate:\n    friend struct SafeCountablePolicy;\n    #ifdef SAFE_PTR_TRACK_CALLSTACKS\n    struct Callstack\n    {\n        Callstack(void* p) : ptr(p) {}\n        void* ptr;\n        Backtrace bt;\n    };\n\n    mutable Vector<Callstack> m_callstacks;\n    #endif\n    mutable int m_count = 0;\n#endif\n};\n\nstruct SafeCountablePolicy\n{\n#ifdef KAK_DEBUG\n    static void inc_ref(const SafeCountable* sc, void* ptr) noexcept\n    {\n        ++sc->m_count;\n        #ifdef SAFE_PTR_TRACK_CALLSTACKS\n        sc->m_callstacks.emplace_back(ptr);\n        #endif\n    }\n\n    static void dec_ref(const SafeCountable* sc, void* ptr) noexcept\n    {\n        --sc->m_count;\n        kak_assert(sc->m_count >= 0);\n        #ifdef SAFE_PTR_TRACK_CALLSTACKS\n        auto it = std::find_if(sc->m_callstacks.begin(), sc->m_callstacks.end(),\n                               [=](const SafeCountable::Callstack& cs) { return cs.ptr == ptr; });\n        kak_assert(it != sc->m_callstacks.end());\n        sc->m_callstacks.erase(it);\n        #endif\n    }\n\n    static void ptr_moved(const SafeCountable* sc, void* from, void* to) noexcept\n    {\n        #ifdef SAFE_PTR_TRACK_CALLSTACKS\n        auto it = std::find_if(sc->m_callstacks.begin(), sc->m_callstacks.end(),\n                               [=](const SafeCountable::Callstack& cs) { return cs.ptr == from; });\n        kak_assert(it != sc->m_callstacks.end());\n        it->ptr = to;\n        #endif\n    }\n#else\n    static void inc_ref(const SafeCountable*, void* ptr) noexcept {}\n    static void dec_ref(const SafeCountable*, void* ptr) noexcept {}\n    static void ptr_moved(const SafeCountable*, void*, void*) noexcept {}\n#endif\n};\n\ntemplate<typename T>\nusing SafePtr = RefPtr<T, SafeCountablePolicy>;\n\n}\n\n#endif // safe_ptr_hh_INCLUDED\n"
  },
  {
    "path": "src/scope.cc",
    "content": "#include \"scope.hh\"\n\n#include \"alias_registry.hh\"\n#include \"face_registry.hh\"\n#include \"highlighter_group.hh\"\n#include \"hook_manager.hh\"\n#include \"keymap_manager.hh\"\n#include \"option_manager.hh\"\n#include \"context.hh\"\n\nnamespace Kakoune\n{\n\nstruct Scope::Data\n{\n    OptionManager options;\n    HookManager   hooks;\n    KeymapManager keymaps;\n    AliasRegistry aliases;\n    FaceRegistry  faces;\n    Highlighters  highlighters;\n};\n\nScope::Scope() : m_data(make_unique_ptr<Data>()) {}\n\nScope::Scope(Scope& parent)\n    : m_data(make_unique_ptr<Data>(parent.options(),\n                                   parent.hooks(),\n                                   parent.keymaps(),\n                                   parent.aliases(),\n                                   parent.faces(),\n                                   parent.highlighters()))\n{}\n\nScope::~Scope() = default;\n\nOptionManager&       Scope::options()            { return m_data->options; }\nconst OptionManager& Scope::options()      const { return m_data->options; }\nHookManager&         Scope::hooks()              { return m_data->hooks; }\nconst HookManager&   Scope::hooks()        const { return m_data->hooks; }\nKeymapManager&       Scope::keymaps()            { return m_data->keymaps; }\nconst KeymapManager& Scope::keymaps()      const { return m_data->keymaps; }\nAliasRegistry&       Scope::aliases()            { return m_data->aliases; }\nconst AliasRegistry& Scope::aliases()      const { return m_data->aliases; }\nFaceRegistry&        Scope::faces()              { return m_data->faces; }\nconst FaceRegistry&  Scope::faces()        const { return m_data->faces; }\nHighlighters&        Scope::highlighters()       { return m_data->highlighters; }\nconst Highlighters&  Scope::highlighters() const { return m_data->highlighters; }\n\nvoid Scope::reparent(Scope& parent)\n{\n    m_data->options.reparent(parent.options());\n    m_data->hooks.reparent(parent.hooks());\n    m_data->keymaps.reparent(parent.keymaps());\n    m_data->aliases.reparent(parent.aliases());\n    m_data->faces.reparent(parent.faces());\n    m_data->highlighters.reparent(parent.highlighters());\n}\n\nstruct GlobalScope::GlobalData final : public OptionWatcher\n{\n    GlobalData(GlobalScope& parent)\n    : m_parent(parent),\n      m_option_registry(parent.options())\n    {\n        m_parent.options().register_watcher(*this);\n    }\n\n    ~GlobalData()\n    {\n        m_parent.options().unregister_watcher(*this);\n    }\n\n    void on_option_changed(const Option& option) override\n    {\n        Context empty_context{Context::EmptyContextFlag{}};\n        m_parent.hooks().run_hook(Hook::GlobalSetOption,\n                                  format(\"{}={}\", option.name(), option.get_desc_string()),\n                                  empty_context);\n    }\n\n    Scope& m_parent;\n    OptionsRegistry m_option_registry;\n};\n\nGlobalScope::GlobalScope()\n    : m_global_data(make_unique_ptr<GlobalData>(*this))\n{\n}\n\nGlobalScope::~GlobalScope() = default;\n\nOptionsRegistry& GlobalScope::option_registry() { return m_global_data->m_option_registry; }\nconst OptionsRegistry& GlobalScope::option_registry() const { return m_global_data->m_option_registry; }\n\n}\n"
  },
  {
    "path": "src/scope.hh",
    "content": "#ifndef scope_hh_INCLUDED\n#define scope_hh_INCLUDED\n\n#include \"unique_ptr.hh\"\n#include \"utils.hh\"\n\nnamespace Kakoune\n{\n\nclass AliasRegistry;\nclass FaceRegistry;\nclass Highlighters;\nclass HookManager;\nclass KeymapManager;\nclass OptionManager;\nclass OptionsRegistry;\n\nclass Scope\n{\npublic:\n    Scope(Scope& parent);\n    ~Scope();\n\n    OptionManager&       options();\n    const OptionManager& options() const;\n    HookManager&         hooks();\n    const HookManager&   hooks() const;\n    KeymapManager&       keymaps();\n    const KeymapManager& keymaps() const;\n    AliasRegistry&       aliases();\n    const AliasRegistry& aliases() const;\n    FaceRegistry&        faces();\n    const FaceRegistry&  faces() const;\n    Highlighters&        highlighters();\n    const Highlighters&  highlighters() const;\n\n    void reparent(Scope& parent);\n\nprivate:\n    friend class GlobalScope;\n    Scope();\n    struct Data;\n    UniquePtr<Data> m_data;\n};\n\nclass GlobalScope : public Scope, public Singleton<GlobalScope>\n{\npublic:\n    GlobalScope();\n    ~GlobalScope();\n\n    OptionsRegistry& option_registry();\n    const OptionsRegistry& option_registry() const;\n\nprivate:\n    struct GlobalData;\n    UniquePtr<GlobalData> m_global_data;\n};\n\n}\n\n#endif // scope_hh_INCLUDED\n"
  },
  {
    "path": "src/selection.cc",
    "content": "#include \"selection.hh\"\n\n#include \"buffer_utils.hh\"\n#include \"changes.hh\"\n#include \"format.hh\"\n#include \"ranges.hh\"\n#include \"string_utils.hh\"\n\nnamespace Kakoune\n{\n\nSelectionList::~SelectionList() = default;\n\nSelectionList::SelectionList(Buffer& buffer, Selection s, size_t timestamp)\n    : m_selections({ std::move(s) }), m_buffer(&buffer), m_timestamp(timestamp)\n{\n    check_invariant();\n}\n\nSelectionList::SelectionList(Buffer& buffer, Selection s)\n    : SelectionList(buffer, std::move(s), buffer.timestamp()) {}\n\nSelectionList::SelectionList(Buffer& buffer, Vector<Selection> list, size_t timestamp)\n    : m_selections(std::move(list)), m_buffer(&buffer), m_timestamp(timestamp)\n{\n    kak_assert(size() > 0);\n    m_main = size() - 1;\n    check_invariant();\n}\n\nSelectionList::SelectionList(Buffer& buffer, Vector<Selection> list)\n    : SelectionList(buffer, std::move(list), buffer.timestamp()) {}\n\nSelectionList::SelectionList(const SelectionList&) = default;\nSelectionList::SelectionList(SelectionList&&) = default;\nSelectionList& SelectionList::operator=(const SelectionList&) = default;\nSelectionList& SelectionList::operator=(SelectionList&&) = default;\n\nvoid SelectionList::remove(size_t index)\n{\n    m_selections.erase(begin() + index);\n    if (index < m_main or m_main == m_selections.size())\n        --m_main;\n}\n\nvoid SelectionList::remove_from(size_t index)\n{\n    kak_assert(index > 0);\n    m_selections.erase(begin() + index, end());\n    if (index <= m_main)\n        m_main = m_selections.size() - 1;\n}\n\nvoid SelectionList::set(Vector<Selection> list, size_t main)\n{\n    kak_assert(main < list.size());\n    m_selections = std::move(list);\n    m_main = main;\n    m_timestamp = m_buffer->timestamp();\n    sort();\n    check_invariant();\n}\n\nbool compare_selections(const Selection& lhs, const Selection& rhs)\n{\n    const auto& lmin = lhs.min(), rmin = rhs.min();\n    return lmin == rmin ? lhs.max() < rhs.max() : lmin < rmin;\n}\n\nnamespace\n{\n\nBufferCoord update_insert(BufferCoord coord, BufferCoord begin, BufferCoord end)\n{\n    if (coord < begin)\n        return coord;\n    if (begin.line == coord.line)\n        coord.column += end.column - begin.column;\n    coord.line += end.line - begin.line;\n    kak_assert(coord.line >= 0 and coord.column >= 0);\n    return coord;\n}\n\n/* For reference\nBufferCoord update_erase(BufferCoord coord, BufferCoord begin, BufferCoord end)\n{\n    if (coord < begin)\n        return coord;\n    if (coord <= end)\n        return begin;\n    if (end.line == coord.line)\n        coord.column -= end.column - begin.column;\n    coord.line -= end.line - begin.line;\n    kak_assert(coord.line >= 0 and coord.column >= 0);\n    return coord;\n} */\n\ntemplate<typename Iterator, typename OverlapsFunc>\nIterator merge_overlapping(Iterator begin, Iterator end, size_t& main, OverlapsFunc overlaps)\n{\n    if (begin == end)\n        return begin;\n\n    kak_assert(std::is_sorted(begin, end, compare_selections));\n    size_t size = end - begin;\n    size_t i = 0;\n    for (size_t j = 1; j < size; ++j)\n    {\n        if (overlaps(begin[i], begin[j]))\n        {\n            begin[i].min() = std::min(begin[i].min(), begin[j].min());\n            begin[i].max() = std::max(begin[i].max(), begin[j].max());\n            if (i < main)\n                --main;\n        }\n        else\n        {\n            ++i;\n            if (i != j)\n                begin[i] = std::move(begin[j]);\n        }\n    }\n    kak_assert(std::is_sorted(begin, begin + i +1, compare_selections));\n    return begin + i + 1;\n}\n\n}\n\nBufferCoord& get_first(Selection& sel) { return sel.min(); }\nBufferCoord& get_last(Selection& sel) { return sel.max(); }\n\nVector<Selection> compute_modified_ranges(const Buffer& buffer, size_t timestamp)\n{\n    Vector<Selection> ranges;\n    auto changes = buffer.changes_since(timestamp);\n    auto change_it = changes.begin();\n    while (change_it != changes.end())\n    {\n        auto forward_end = forward_sorted_until(change_it, changes.end());\n        auto backward_end = backward_sorted_until(change_it, changes.end());\n\n        kak_assert(std::is_sorted(ranges.begin(), ranges.end(), compare_selections));\n\n        size_t prev_size;\n        size_t dummy = 0;\n        if (forward_end >= backward_end)\n        {\n            update_forward({ change_it, forward_end }, ranges);\n            ranges.erase(merge_overlapping(ranges.begin(), ranges.end(), dummy, overlaps), ranges.end());\n            prev_size = ranges.size();\n\n            ForwardChangesTracker changes_tracker;\n            for (; change_it != forward_end; ++change_it)\n            {\n                if (change_it->type == Buffer::Change::Insert)\n                    ranges.emplace_back(change_it->begin, change_it->end);\n                else\n                    ranges.emplace_back(change_it->begin);\n                changes_tracker.update(*change_it);\n            }\n        }\n        else\n        {\n            update_backward({ change_it, backward_end }, ranges);\n            ranges.erase(merge_overlapping(ranges.begin(), ranges.end(), dummy, overlaps), ranges.end());\n            prev_size = ranges.size();\n\n            using ReverseIt = std::reverse_iterator<const Buffer::Change*>;\n            ForwardChangesTracker changes_tracker;\n            for (ReverseIt it{backward_end}, end{change_it}; it != end; ++it)\n            {\n                auto change = *it;\n                change.begin = changes_tracker.get_new_coord(change.begin);\n                change.end = changes_tracker.get_new_coord(change.end);\n\n                if (change.type == Buffer::Change::Insert)\n                    ranges.emplace_back(change.begin, change.end);\n                else\n                    ranges.emplace_back(change.begin);\n                changes_tracker.update(change);\n            }\n            change_it = backward_end;\n        }\n\n        kak_assert(std::is_sorted(ranges.begin() + prev_size, ranges.end(), compare_selections));\n        std::inplace_merge(ranges.begin(), ranges.begin() + prev_size, ranges.end(), compare_selections);\n        // The newly added ranges might be overlapping pre-existing ones\n        ranges.erase(merge_overlapping(ranges.begin(), ranges.end(), dummy, overlaps), ranges.end());\n    }\n\n    const auto end_coord = buffer.end_coord();\n    for (auto& range : ranges)\n    {\n        range.anchor() = std::min(range.anchor(), end_coord);\n        range.cursor() = std::min<BufferCoord>(range.cursor(), end_coord);\n    }\n\n    auto touches = [&](const Selection& lhs, const Selection& rhs) {\n        return lhs.max() == end_coord or buffer.char_next(lhs.max()) >= rhs.min();\n    };\n    size_t dummy = 0;\n    ranges.erase(merge_overlapping(ranges.begin(), ranges.end(), dummy, touches), ranges.end());\n\n    for (auto& sel : ranges)\n    {\n        kak_assert(buffer.is_valid(sel.anchor()));\n        kak_assert(buffer.is_valid(sel.cursor()));\n\n        if (buffer.is_end(sel.anchor()))\n            sel.anchor() = buffer.back_coord();\n        if (buffer.is_end(sel.cursor()))\n            sel.cursor() = buffer.back_coord();\n\n        if (sel.anchor() != sel.cursor())\n            sel.cursor() = buffer.char_prev(sel.cursor());\n    }\n    return ranges;\n}\n\nstatic void clamp(Selection& sel, const Buffer& buffer)\n{\n    sel.anchor() = buffer.clamp(sel.anchor());\n    sel.cursor() = buffer.clamp(sel.cursor());\n}\n\nvoid clamp_selections(Vector<Selection>& selections, const Buffer& buffer)\n{\n    for (auto& sel : selections)\n        clamp(sel, buffer);\n}\n\nvoid update_selections(Vector<Selection>& selections, size_t& main, const Buffer& buffer, size_t timestamp, bool merge)\n{\n    if (timestamp == buffer.timestamp())\n        return;\n\n    auto changes = buffer.changes_since(timestamp);\n    auto change_it = changes.begin();\n    while (change_it != changes.end())\n    {\n        auto forward_end = forward_sorted_until(change_it, changes.end());\n        auto backward_end = backward_sorted_until(change_it, changes.end());\n\n        if (forward_end >= backward_end)\n        {\n            update_forward({ change_it, forward_end }, selections);\n            change_it = forward_end;\n        }\n        else\n        {\n            update_backward({ change_it, backward_end }, selections);\n            change_it = backward_end;\n        }\n        kak_assert(std::is_sorted(selections.begin(), selections.end(),\n                                  compare_selections));\n        if (merge)\n            selections.erase(\n                merge_overlapping(selections.begin(), selections.end(),\n                                  main, overlaps), selections.end());\n    }\n    for (auto& sel : selections)\n        clamp(sel, buffer);\n\n    if (merge)\n        selections.erase(merge_overlapping(selections.begin(), selections.end(),\n                                           main, overlaps), selections.end());\n}\n\nvoid SelectionList::update(bool merge)\n{\n    update_selections(m_selections, m_main, *m_buffer, m_timestamp, merge);\n    check_invariant();\n    m_timestamp = m_buffer->timestamp();\n}\n\nvoid SelectionList::check_invariant() const\n{\n#ifdef KAK_DEBUG\n    auto& buffer = this->buffer();\n    kak_assert(size() > 0);\n    kak_assert(m_main < size());\n    const size_t timestamp = buffer.timestamp();\n    kak_assert(timestamp >= m_timestamp);\n\n    // cannot check further in that case\n    if (timestamp != m_timestamp)\n        return;\n\n    const auto end_coord = buffer.end_coord();\n    BufferCoord last_min{0,0};\n    for (auto& sel : m_selections)\n    {\n        auto& min = sel.min();\n        kak_assert(min >= last_min);\n        last_min = min;\n\n        const auto anchor = sel.anchor();\n        kak_assert(anchor >= BufferCoord{0,0} and anchor < end_coord);\n        kak_assert(anchor.column < buffer[anchor.line].length());\n\n        const auto cursor = sel.cursor();\n        kak_assert(cursor >= BufferCoord{0,0} and cursor < end_coord);\n        kak_assert(cursor.column < buffer[cursor.line].length());\n    }\n#endif\n}\n\nvoid sort_selections(Vector<Selection>& selections, size_t& main_index)\n{\n    if (selections.size() == 1)\n        return;\n\n    const auto& main = selections[main_index];\n    const auto main_begin = main.min();\n    main_index = std::count_if(selections.begin(), selections.end(),\n                               [&](const Selection& sel) {\n        auto begin = sel.min();\n        if (begin == main_begin)\n            return &sel < &main;\n        else\n            return begin < main_begin;\n    });\n    std::stable_sort(selections.begin(), selections.end(), compare_selections);\n}\n\nvoid merge_overlapping_selections(Vector<Selection>& selections, size_t& main_index)\n{\n    if (selections.size() == 1)\n        return;\n\n    selections.erase(Kakoune::merge_overlapping(selections.begin(), selections.end(),\n                                                main_index, overlaps), selections.end());\n}\n\nvoid SelectionList::sort()\n{\n    sort_selections(m_selections, m_main);\n}\n\nvoid SelectionList::merge_overlapping()\n{\n    merge_overlapping_selections(m_selections, m_main);\n}\n\nvoid SelectionList::merge_consecutive()\n{\n    if (size() == 1)\n        return;\n\n    auto touches = [this](const Selection& lhs, const Selection& rhs) {\n        return m_buffer->char_next(lhs.max()) >= rhs.min();\n    };\n    m_selections.erase(Kakoune::merge_overlapping(begin(), end(),\n                                                  m_main, touches), end());\n}\n\nvoid SelectionList::sort_and_merge_overlapping()\n{\n    sort();\n    merge_overlapping();\n}\n\nstatic void fix_overflowing_selections(Vector<Selection>& selections,\n                                       const Buffer& buffer)\n{\n    const BufferCoord back_coord = buffer.back_coord();\n    for (auto& sel : selections)\n    {\n        sel.cursor() = std::min(buffer.clamp(sel.cursor()), back_coord);\n        sel.anchor() = std::min(buffer.clamp(sel.anchor()), back_coord);\n    }\n}\n\nbool any_overlaps(ConstArrayView<Selection> sels)\n{\n    for (int i = 0; i + 1 < sels.size(); ++i)\n    {\n        if (overlaps(sels[i], sels[i+1]))\n            return true;\n    }\n    return false;\n}\n\nvoid SelectionList::for_each(ApplyFunc func, bool may_append)\n{\n    update();\n\n    if (may_append and any_overlaps(m_selections))\n    {\n        size_t timestamp = m_buffer->timestamp();\n        for (size_t index = 0; index < m_selections.size(); ++index)\n        {\n            auto& sel = m_selections[index];\n            update_ranges(*m_buffer, timestamp, ArrayView<Selection>(sel));\n            func(index, sel);\n        }\n    }\n    else\n    {\n        ForwardChangesTracker changes_tracker;\n        for (size_t index = 0; index < m_selections.size(); ++index)\n        {\n            auto& sel = m_selections[index];\n\n            sel.anchor() = changes_tracker.get_new_coord_tolerant(sel.anchor());\n            sel.cursor() = changes_tracker.get_new_coord_tolerant(sel.cursor());\n            kak_assert(m_buffer->is_valid(sel.anchor()) and m_buffer->is_valid(sel.cursor()));\n\n            func(index, sel);\n\n            changes_tracker.update(*m_buffer, m_timestamp);\n        }\n    }\n\n    // We might just have been deleting text if strings were empty,\n    // in which case we could have some selections pushed out of the buffer\n    fix_overflowing_selections(m_selections, *m_buffer);\n\n    check_invariant();\n    m_buffer->check_invariant();\n}\n\n\nvoid replace(Buffer& buffer, Selection& sel, StringView content)\n{\n    // we want min and max from *before* we do any change\n    auto& min = sel.min();\n    auto& max = sel.max();\n    BufferRange range = buffer.replace(min, buffer.char_next(max), content);\n    min = range.begin;\n    max = range.end > range.begin ? buffer.char_prev(range.end) : range.begin;\n}\n\nBufferRange insert(Buffer& buffer, Selection& sel, BufferCoord pos, StringView content)\n{\n    auto range = buffer.insert(pos, content);\n    sel.anchor() = buffer.clamp(update_insert(sel.anchor(), range.begin, range.end));\n    sel.cursor() = buffer.clamp(update_insert(sel.cursor(), range.begin, range.end));\n    return range;\n}\n\nvoid SelectionList::replace(ConstArrayView<String> strings)\n{\n    if (strings.empty())\n        return;\n\n    for_each([&](size_t index, Selection& sel) {\n        Kakoune::replace(*m_buffer, sel, strings[std::min(strings.size()-1, index)]);\n    }, false);\n}\n\nvoid SelectionList::erase()\n{\n    update();\n    merge_overlapping();\n\n    ForwardChangesTracker changes_tracker;\n    for (auto& sel : m_selections)\n    {\n        sel.anchor() = changes_tracker.get_new_coord(sel.anchor());\n        kak_assert(m_buffer->is_valid(sel.anchor()));\n        sel.cursor() = changes_tracker.get_new_coord(sel.cursor());\n        kak_assert(m_buffer->is_valid(sel.cursor()));\n\n        auto pos = Kakoune::erase(*m_buffer, sel);\n        sel.anchor() = sel.cursor() = pos;\n        changes_tracker.update(*m_buffer, m_timestamp);\n    }\n\n    fix_overflowing_selections(m_selections, *m_buffer);\n    m_buffer->check_invariant();\n}\n\nString selection_to_string(ColumnType column_type, const Buffer& buffer, const Selection& selection, ColumnCount tabstop)\n{\n    const auto& cursor = selection.cursor();\n    const auto& anchor = selection.anchor();\n    switch (column_type)\n    {\n    default:\n    case ColumnType::Byte:\n        return format(\"{}.{},{}.{}\", anchor.line + 1, anchor.column + 1,\n                      cursor.line + 1, cursor.column + 1);\n    case ColumnType::Codepoint:\n        return format(\"{}.{},{}.{}\",\n                      anchor.line + 1, buffer[anchor.line].char_count_to(anchor.column) + 1,\n                      cursor.line + 1, buffer[cursor.line].char_count_to(cursor.column) + 1);\n    case ColumnType::DisplayColumn:\n        kak_assert(tabstop != -1);\n        return format(\"{}.{},{}.{}\",\n                      anchor.line + 1, get_column(buffer, tabstop, anchor) + 1,\n                      cursor.line + 1, get_column(buffer, tabstop, cursor) + 1);\n    }\n}\n\nString selection_list_to_string(ColumnType column_type, const SelectionList& selections, ColumnCount tabstop)\n{\n    auto& buffer = selections.buffer();\n    kak_assert(selections.timestamp() == buffer.timestamp());\n\n    auto to_string = [&](const Selection& selection) {\n        return selection_to_string(column_type, buffer, selection, tabstop);\n    };\n\n    auto beg = &*selections.begin(), end = &*selections.end();\n    auto main = beg + selections.main_index();\n    using View = ConstArrayView<Selection>;\n    return join(concatenated(View{main, end}, View{beg, main}) |\n                transform(to_string), ' ', false);\n}\n\nSelection selection_from_string(ColumnType column_type, const Buffer& buffer, StringView desc, ColumnCount tabstop)\n{\n    auto comma = find(desc, ',');\n    auto dot_anchor = find(StringView{desc.begin(), comma}, '.');\n    auto dot_cursor = find(StringView{comma, desc.end()}, '.');\n\n    if (comma == desc.end() or dot_anchor == comma or dot_cursor == desc.end())\n        throw runtime_error(format(\"'{}' does not follow <line>.<column>,<line>.<column> format\", desc));\n\n    auto compute_coord = [&](int line, int column) -> BufferCoord {\n        if (line < 0 or column < 0)\n            throw runtime_error(format(\"coordinate {}.{} does not exist in buffer\", line + 1, column + 1));\n\n        switch (column_type)\n        {\n        default:\n        case ColumnType::Byte: return {line, column};\n        case ColumnType::Codepoint:\n            if (buffer.line_count() <= line or buffer[line].char_length() <= column)\n                throw runtime_error(format(\"coordinate {}.{} does not exist in buffer\", line + 1, column + 1));\n            return {line, buffer[line].byte_count_to(CharCount{column})};\n        case ColumnType::DisplayColumn:\n            kak_assert(tabstop != -1);\n            if (buffer.line_count() <= line or column_length(buffer, tabstop, line) <= column)\n                throw runtime_error(format(\"coordinate {}.{} does not exist in buffer\", line + 1, column + 1));\n            return {line, get_byte_to_column(buffer, tabstop, DisplayCoord{line, ColumnCount{column}})};\n        }\n    };\n\n    auto anchor = compute_coord(str_to_int({desc.begin(), dot_anchor}) - 1,\n                                str_to_int({dot_anchor+1, comma}) - 1);\n\n    auto cursor = compute_coord(str_to_int({comma+1, dot_cursor}) - 1,\n                                str_to_int({dot_cursor+1, desc.end()}) - 1);\n\n    return Selection{anchor, cursor};\n}\n\nSelectionList selection_list_from_strings(Buffer& buffer, ColumnType column_type, ConstArrayView<String> descs, size_t timestamp, size_t main, ColumnCount tabstop)\n{\n    if ((column_type != ColumnType::Byte and timestamp != buffer.timestamp()) or timestamp > buffer.timestamp())\n        throw runtime_error{format(\"invalid timestamp '{}'\", timestamp)};\n\n    auto from_string = [&](StringView desc) {\n        return selection_from_string(column_type, buffer, desc, tabstop);\n    };\n\n    auto sels = descs | transform(from_string) | gather<Vector<Selection>>();\n    if (sels.empty())\n        throw runtime_error{\"empty selection description\"};\n    if (main >= sels.size())\n        throw runtime_error{\"invalid main selection index\"};\n\n    sort_selections(sels, main);\n    merge_overlapping_selections(sels, main);\n    if (timestamp < buffer.timestamp())\n        update_selections(sels, main, buffer, timestamp);\n    else\n        clamp_selections(sels, buffer);\n\n    SelectionList res{buffer, std::move(sels)};\n    res.set_main_index(main);\n    return res;\n}\n\n}\n"
  },
  {
    "path": "src/selection.hh",
    "content": "#ifndef selection_hh_INCLUDED\n#define selection_hh_INCLUDED\n\n#include \"coord.hh\"\n#include \"range.hh\"\n#include \"safe_ptr.hh\"\n#include \"utils.hh\"\n#include \"string.hh\"\n#include \"vector.hh\"\n\n#include <limits>\n\nnamespace Kakoune\n{\n\nclass Buffer;\nusing BufferRange = Range<BufferCoord>;\n\nusing CaptureList = Vector<String, MemoryDomain::Selections>;\n\nconstexpr ColumnCount max_column{std::numeric_limits<int>::max()};\nconstexpr ColumnCount max_non_eol_column{max_column-1};\n\nstruct BasicSelection\n{\n    static constexpr MemoryDomain Domain = MemoryDomain::Selections;\n\n    BasicSelection() = default;\n    BasicSelection(BufferCoord pos) : BasicSelection(pos,pos) {}\n    BasicSelection(BufferCoordAndTarget pos) : BasicSelection(pos,pos) {}\n    BasicSelection(BufferCoord anchor, BufferCoordAndTarget cursor)\n        : m_anchor{anchor}, m_cursor{cursor} {}\n\n    BufferCoord& anchor() { return m_anchor; }\n    BufferCoordAndTarget& cursor() { return m_cursor; }\n\n    const BufferCoord& anchor() const { return m_anchor; }\n    const BufferCoordAndTarget& cursor() const { return m_cursor; }\n\n    void set(BufferCoord anchor, BufferCoord cursor)\n    {\n        m_anchor = anchor;\n        m_cursor = cursor;\n    }\n\n    void set(BufferCoord coord) { set(coord, coord); }\n\n    friend bool operator==(const BasicSelection&, const BasicSelection&) = default;\n\n    // When selections are single char, we want the anchor to be considered min, and cursor max\n    const BufferCoord& min() const { return m_anchor <= m_cursor ? m_anchor : m_cursor; }\n    const BufferCoord& max() const { return m_anchor <= m_cursor ? m_cursor : m_anchor; }\n\n    BufferCoord& min() { return m_anchor <= m_cursor ? m_anchor : m_cursor; }\n    BufferCoord& max() { return m_anchor <= m_cursor ? m_cursor : m_anchor; }\n\nprivate:\n    BufferCoord m_anchor;\n    BufferCoordAndTarget m_cursor;\n};\n\nstruct Selection : BasicSelection\n{\n    Selection() = default;\n    Selection(BufferCoord pos) : BasicSelection(pos,pos) {}\n    Selection(BufferCoordAndTarget pos) : BasicSelection(pos,pos) {}\n    Selection(BufferCoord anchor, BufferCoordAndTarget cursor, CaptureList captures = {})\n        : BasicSelection{anchor, cursor}, m_captures(std::move(captures)) {}\n    CaptureList& captures() { return m_captures; }\n    const CaptureList& captures() const { return m_captures; }\n\n    friend bool operator==(const Selection&, const Selection&) = default;\n\nprivate:\n    CaptureList m_captures;\n};\n\ninline bool overlaps(const BasicSelection& lhs, const BasicSelection& rhs)\n{\n    return lhs.min() <= rhs.min() ? lhs.max() >= rhs.min()\n                                  : lhs.min() <= rhs.max();\n}\n\nvoid update_selections(Vector<Selection>& selections, size_t& main,\n                       const Buffer& buffer, size_t timestamp, bool merge = true);\n\nbool compare_selections(const Selection& lhs, const Selection& rhs);\nvoid sort_selections(Vector<Selection>& selections, size_t& main);\nvoid merge_overlapping_selections(Vector<Selection>& selections, size_t& main);\nvoid clamp_selections(Vector<Selection>& sel, const Buffer& buffer);\n\nvoid replace(Buffer& buffer, Selection& sel, StringView content);\nBufferRange insert(Buffer& buffer, Selection& sel, BufferCoord pos, StringView content);\n\nstruct SelectionList\n{\n    static constexpr MemoryDomain Domain = MemoryDomain::Selections;\n\n    ~SelectionList();\n    SelectionList(Buffer& buffer, Selection s);\n    SelectionList(Buffer& buffer, Selection s, size_t timestamp);\n    SelectionList(Buffer& buffer, Vector<Selection> s);\n    SelectionList(Buffer& buffer, Vector<Selection> s, size_t timestamp);\n\n    SelectionList(const SelectionList&);\n    SelectionList(SelectionList&&);\n\n    SelectionList& operator=(const SelectionList&);\n    SelectionList& operator=(SelectionList&&);\n\n    void update(bool merge = true);\n    void check_invariant() const;\n\n    const Selection& main() const { return (*this)[m_main]; }\n    Selection& main() { return (*this)[m_main]; }\n    size_t main_index() const { return m_main; }\n    void set_main_index(size_t main) { kak_assert(main < size()); m_main = main; }\n\n    void push_back(const Selection& sel) { m_selections.push_back(sel); }\n    void push_back(Selection&& sel) { m_selections.push_back(std::move(sel)); }\n\n    Selection& operator[](size_t i) { return m_selections[i]; }\n    const Selection& operator[](size_t i) const { return m_selections[i]; }\n\n    void set(Vector<Selection> list, size_t main);\n    SelectionList& operator=(Vector<Selection> list)\n    {\n        const size_t main_index = list.size()-1;\n        set(std::move(list), main_index);\n        return *this;\n    }\n\n    using iterator = Vector<Selection>::iterator;\n    iterator begin() { return m_selections.begin(); }\n    iterator end() { return m_selections.end(); }\n\n    using const_iterator = Vector<Selection>::const_iterator;\n    const_iterator begin() const { return m_selections.begin(); }\n    const_iterator end() const { return m_selections.end(); }\n\n    void remove(size_t index);\n    void remove_from(size_t index);\n\n    const Selection* data() const { return m_selections.data(); }\n    size_t size() const { return m_selections.size(); }\n\n    bool operator==(const SelectionList& other) const { return m_buffer == other.m_buffer and m_selections == other.m_selections; }\n\n    void sort();\n    void merge_overlapping();\n    void merge_consecutive();\n    void sort_and_merge_overlapping();\n\n    Buffer& buffer() const { return *m_buffer; }\n\n    size_t timestamp() const { return m_timestamp; }\n    void force_timestamp(size_t timestamp) { m_timestamp = timestamp; }\n\n    using ApplyFunc = FunctionRef<void (size_t index, Selection& sel)>;\n    void for_each(ApplyFunc apply, bool may_append);\n\n    void replace(ConstArrayView<String> strings);\n\n    void erase();\n\nprivate:\n    size_t m_main = 0;\n    Vector<Selection> m_selections;\n\n    SafePtr<Buffer> m_buffer;\n    size_t m_timestamp;\n};\n\nVector<Selection> compute_modified_ranges(const Buffer& buffer, size_t timestamp);\n\nenum class ColumnType\n{\n    Byte,\n    Codepoint,\n    DisplayColumn\n};\n\nSelection selection_from_string(ColumnType column_type, const Buffer& buffer, StringView desc, ColumnCount tabstop = -1);\nString selection_to_string(ColumnType column_type, const Buffer& buffer, const Selection& selection, ColumnCount tabstop = -1);\n\nString selection_list_to_string(ColumnType column_type, const SelectionList& selections, ColumnCount tabstop = -1);\n\nSelectionList selection_list_from_strings(Buffer& buffer, ColumnType column_type, ConstArrayView<String> descs, size_t timestamp, size_t main, ColumnCount tabstop = -1);\n\n}\n\n#endif // selection_hh_INCLUDED\n"
  },
  {
    "path": "src/selectors.cc",
    "content": "#include \"selectors.hh\"\n\n#include \"buffer_utils.hh\"\n#include \"context.hh\"\n#include \"event_manager.hh\"\n#include \"flags.hh\"\n#include \"option_manager.hh\"\n#include \"option_types.hh\"\n#include \"regex.hh\"\n#include \"selection.hh\"\n#include \"string.hh\"\n#include \"unit_tests.hh\"\n#include \"utf8_iterator.hh\"\n\n#include <algorithm>\n\nnamespace Kakoune\n{\n\nusing Utf8Iterator = utf8::iterator<BufferIterator>;\n\nnamespace\n{\n\nSelection utf8_range(const BufferIterator& first, const BufferIterator& last)\n{\n    return {first.coord(), last.coord()};\n}\n\nSelection utf8_range(const Utf8Iterator& first, const Utf8Iterator& last)\n{\n    return {first.base().coord(), last.base().coord()};\n}\n\nConstArrayView<Codepoint> get_extra_word_chars(const Context& context)\n{\n    return context.options()[\"extra_word_chars\"].get<Vector<Codepoint, MemoryDomain::Options>>();\n}\n\n}\n\nSelection keep_direction(Selection res, const Selection& ref)\n{\n    if ((res.cursor() < res.anchor()) != (ref.cursor() < ref.anchor()))\n        std::swap<BufferCoord>(res.cursor(), res.anchor());\n    return res;\n}\n\ntemplate<WordType word_type>\nOptional<Selection>\nselect_to_next_word(const Context& context, const Selection& selection)\n{\n    auto extra_word_chars = get_extra_word_chars(context);\n    auto& buffer = context.buffer();\n    Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer};\n    if (begin+1 == buffer.end())\n        return {};\n    if (categorize<word_type>(*begin, extra_word_chars) !=\n        categorize<word_type>(*(begin+1), extra_word_chars))\n        ++begin;\n\n    if (not skip_while(begin, buffer.end(),\n                       [](Codepoint c) { return is_eol(c); }))\n        return {};\n    Utf8Iterator end = begin+1;\n\n    auto is_word = [&](Codepoint c) { return Kakoune::is_word<word_type>(c, extra_word_chars); };\n    auto is_punctuation = [&](Codepoint c) { return Kakoune::is_punctuation(c, extra_word_chars); };\n\n    if (is_word(*begin))\n        skip_while(end, buffer.end(), is_word);\n    else if (is_punctuation(*begin))\n        skip_while(end, buffer.end(), is_punctuation);\n\n    skip_while(end, buffer.end(), is_horizontal_blank);\n\n    return utf8_range(begin, end-1);\n}\ntemplate Optional<Selection> select_to_next_word<WordType::Word>(const Context&, const Selection&);\ntemplate Optional<Selection> select_to_next_word<WordType::WORD>(const Context&, const Selection&);\n\ntemplate<WordType word_type>\nOptional<Selection>\nselect_to_next_word_end(const Context& context, const Selection& selection)\n{\n    auto extra_word_chars = get_extra_word_chars(context);\n    auto& buffer = context.buffer();\n    Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer};\n    if (begin+1 == buffer.end())\n        return {};\n    if (categorize<word_type>(*begin, extra_word_chars) !=\n        categorize<word_type>(*(begin+1), extra_word_chars))\n        ++begin;\n\n    if (not skip_while(begin, buffer.end(),\n                       [](Codepoint c) { return is_eol(c); }))\n        return {};\n    Utf8Iterator end = begin;\n    skip_while(end, buffer.end(), is_horizontal_blank);\n\n    auto is_word = [&](Codepoint c) { return Kakoune::is_word<word_type>(c, extra_word_chars); };\n    auto is_punctuation = [&](Codepoint c) { return Kakoune::is_punctuation(c, extra_word_chars); };\n\n    if (is_word(*end))\n        skip_while(end, buffer.end(), is_word);\n    else if (is_punctuation(*end))\n        skip_while(end, buffer.end(), is_punctuation);\n\n    return utf8_range(begin, end-1);\n}\ntemplate Optional<Selection> select_to_next_word_end<WordType::Word>(const Context&, const Selection&);\ntemplate Optional<Selection> select_to_next_word_end<WordType::WORD>(const Context&, const Selection&);\n\ntemplate<WordType word_type>\nOptional<Selection>\nselect_to_previous_word(const Context& context, const Selection& selection)\n{\n    auto extra_word_chars = get_extra_word_chars(context);\n    auto& buffer = context.buffer();\n    Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer};\n    if (begin == buffer.begin())\n        return {};\n    if (categorize<word_type>(*begin, extra_word_chars) !=\n        categorize<word_type>(*(begin-1), extra_word_chars))\n        --begin;\n\n    skip_while_reverse(begin, buffer.begin(), [](Codepoint c){ return is_eol(c); });\n    Utf8Iterator end = begin;\n\n    auto is_word = [&](Codepoint c) { return Kakoune::is_word<word_type>(c, extra_word_chars); };\n    auto is_punctuation = [&](Codepoint c) { return Kakoune::is_punctuation(c, extra_word_chars); };\n\n    bool with_end = skip_while_reverse(end, buffer.begin(), is_horizontal_blank);\n    if (is_word(*end))\n        with_end = skip_while_reverse(end, buffer.begin(), is_word);\n    else if (is_punctuation(*end))\n        with_end = skip_while_reverse(end, buffer.begin(), is_punctuation);\n\n    return utf8_range(begin, with_end ? end : end+1);\n}\ntemplate Optional<Selection> select_to_previous_word<WordType::Word>(const Context&, const Selection&);\ntemplate Optional<Selection> select_to_previous_word<WordType::WORD>(const Context&, const Selection&);\n\ntemplate<WordType word_type>\nOptional<Selection> select_word(const Context& context, const Selection& selection, int count, ObjectFlags flags)\n{\n    auto extra_word_chars = get_extra_word_chars(context);\n    auto& buffer = context.buffer();\n\n    auto is_word = [&](Codepoint c) { return Kakoune::is_word<word_type>(c, extra_word_chars); };\n\n    Utf8Iterator first{buffer.iterator_at(selection.cursor()), buffer};\n    if (not is_word(*first))\n        return {};\n\n    Utf8Iterator last = first;\n    if (flags & ObjectFlags::ToBegin)\n    {\n        skip_while_reverse(first, buffer.begin(), is_word);\n        if (not is_word(*first))\n            ++first;\n    }\n    if (flags & ObjectFlags::ToEnd)\n    {\n        skip_while(last, buffer.end(), is_word);\n        if (not (flags & ObjectFlags::Inner))\n            skip_while(last, buffer.end(), is_horizontal_blank);\n        --last;\n    }\n    return (flags & ObjectFlags::ToEnd) ? utf8_range(first, last)\n                                        : utf8_range(last, first);\n}\ntemplate Optional<Selection> select_word<WordType::Word>(const Context&, const Selection&, int, ObjectFlags);\ntemplate Optional<Selection> select_word<WordType::WORD>(const Context&, const Selection&, int, ObjectFlags);\n\ntemplate<bool only_move>\nOptional<Selection> select_to_line_end(const Context& context, const Selection& selection)\n{\n    auto& buffer = context.buffer();\n    BufferCoord begin = selection.cursor();\n    LineCount line = begin.line;\n    BufferCoord end = utf8::previous(buffer.iterator_at({line, buffer[line].length() - 1}),\n                                     buffer.iterator_at(line)).coord();\n    if (end < begin) // Do not go backward when cursor is on eol\n        end = begin;\n    return Selection{only_move ? end : begin, {end, max_non_eol_column}};\n}\ntemplate Optional<Selection> select_to_line_end<false>(const Context&, const Selection&);\ntemplate Optional<Selection> select_to_line_end<true>(const Context&, const Selection&);\n\ntemplate<bool only_move>\nOptional<Selection> select_to_line_begin(const Context&, const Selection& selection)\n{\n    BufferCoord begin = selection.cursor();\n    BufferCoord end = begin.line;\n    return Selection{only_move ? end : begin, end};\n}\ntemplate Optional<Selection> select_to_line_begin<false>(const Context&, const Selection&);\ntemplate Optional<Selection> select_to_line_begin<true>(const Context&, const Selection&);\n\nOptional<Selection> select_to_first_non_blank(const Context& context, const Selection& selection)\n{\n    auto& buffer = context.buffer();\n    auto it = buffer.iterator_at(selection.cursor().line);\n    skip_while(it, buffer.iterator_at(selection.cursor().line+1),\n               is_horizontal_blank);\n    return {it.coord()};\n}\n\ntemplate<bool forward>\nOptional<Selection> select_matching(const Context& context, const Selection& selection)\n{\n    auto& buffer = context.buffer();\n    auto& matching_pairs = context.options()[\"matching_pairs\"].get<Vector<Codepoint, MemoryDomain::Options>>();\n    Utf8Iterator it{buffer.iterator_at(selection.cursor()), buffer};\n    auto match = matching_pairs.end();\n\n    if (forward) while (it != buffer.end())\n    {\n        match = find(matching_pairs, *it);\n        if (match != matching_pairs.end())\n            break;\n        ++it;\n    }\n    else while (true)\n    {\n        match = find(matching_pairs, *it);\n        if (match != matching_pairs.end()\n            or it == buffer.begin())\n            break;\n        --it;\n    }\n\n    if (match == matching_pairs.end())\n        return {};\n\n    Utf8Iterator begin = it;\n\n    if (((match - matching_pairs.begin()) % 2) == 0)\n    {\n        int level = 0;\n        const Codepoint opening = *match;\n        const Codepoint closing = *(match+1);\n        while (it != buffer.end())\n        {\n            if (*it == opening)\n                ++level;\n            else if (*it == closing and --level == 0)\n                return utf8_range(begin, it);\n            ++it;\n        }\n    }\n    else\n    {\n        int level = 0;\n        const Codepoint opening = *(match-1);\n        const Codepoint closing = *match;\n        while (true)\n        {\n            if (*it == closing)\n                ++level;\n            else if (*it == opening and --level == 0)\n                return utf8_range(begin, it);\n            if (it == buffer.begin())\n                break;\n            --it;\n        }\n    }\n    return {};\n}\ntemplate Optional<Selection>\nselect_matching<true>(const Context& context, const Selection& selection);\ntemplate Optional<Selection>\nselect_matching<false>(const Context& context, const Selection& selection);\n\ntemplate<typename Iterator, typename Container>\nOptional<std::pair<Iterator, Iterator>>\nfind_opening(Iterator pos, const Container& container,\n             const Regex& opening, const Regex& closing,\n             int level, bool nestable)\n{\n    MatchResults<Iterator> res;\n    // When on the token of a non-nestable block, we want to consider it opening\n    if (nestable and\n        backward_regex_search(container.begin(), pos,\n                              container.begin(), container.end(), res, closing,\n                              RegexExecFlags::None, EventManager::handle_urgent_events) and\n        res[0].second == pos)\n        pos = res[0].first;\n\n    using RegexIt = RegexIterator<Iterator, RegexMode::Backward, const Regex, void (*)()>;\n    for (auto&& match : RegexIt{container.begin(), pos, container.begin(), container.end(), opening,\n                                RegexExecFlags::None, EventManager::handle_urgent_events})\n    {\n        if (nestable)\n        {\n            for (auto m [[maybe_unused]] : RegexIt{match[0].second, pos, container.begin(), container.end(), closing,\n                                                   RegexExecFlags::None, EventManager::handle_urgent_events})\n                ++level;\n        }\n\n        if (not nestable or level == 0)\n            return match[0];\n        pos = match[0].first;\n        --level;\n    }\n    return {};\n}\n\ntemplate<typename Iterator, typename Container>\nOptional<std::pair<Iterator, Iterator>>\nfind_closing(Iterator pos, const Container& container,\n             const Regex& opening, const Regex& closing,\n             int level, bool nestable)\n{\n    for (auto match : RegexIterator{pos, container.end(), container.begin(), container.end(), closing,\n                                    RegexExecFlags::None, EventManager::handle_urgent_events})\n    {\n        if (nestable)\n        {\n            for (auto m [[maybe_unused]] : RegexIterator{pos, match[0].first, container.begin(), container.end(), opening,\n                                                         RegexExecFlags::None, EventManager::handle_urgent_events})\n                ++level;\n        }\n\n        if (not nestable or level == 0)\n            return match[0];\n        pos = match[0].second;\n        --level;\n    }\n    return {};\n}\n\ntemplate<typename Container, typename Iterator>\nOptional<std::pair<Iterator, Iterator>>\nfind_surrounding(const Container& container, Iterator pos,\n                 const Regex& opening, const Regex& closing,\n                 ObjectFlags flags, int level)\n{\n    auto empty = [](const std::pair<Iterator, Iterator>& m) { return m.first == m.second; };\n    const bool nestable = opening != closing;\n    auto first = pos;\n    auto last = pos;\n    if (flags & ObjectFlags::ToBegin)\n    {\n        if (auto res = find_opening(first+1, container, opening, closing, level, nestable))\n        {\n            first = (flags & ObjectFlags::Inner) ? res->second : res->first;\n            if (flags & ObjectFlags::ToEnd) // ensure we find the matching end\n            {\n                last = empty(*res) ? std::next(res->second) : res->second;\n                level = 0;\n            }\n        }\n        else\n            return {};\n    }\n    else if (MatchResults<Iterator> res;\n             regex_search(pos, container.end(), container.begin(), container.end(), res, opening,\n                          RegexExecFlags::None, EventManager::handle_urgent_events) and\n             res[0].first == pos) // Skip opening match if pos lies on it\n        last = empty(res[0]) ? std::next(res[0].second) : res[0].second;\n\n    if (flags & ObjectFlags::ToEnd)\n    {\n        if (auto res = find_closing(last, container, opening, closing, level, nestable))\n            last = utf8::previous((flags & ObjectFlags::Inner) ? res->first : res->second,\n                                  container.begin());\n        else\n            return {};\n    }\n    if (first > last)\n        return {};\n\n    return std::pair<Iterator, Iterator>{first, last};\n}\n\nOptional<Selection>\nselect_surrounding(const Context& context, const Selection& selection,\n                   const Regex& opening, const Regex& closing, int level,\n                   ObjectFlags flags)\n{\n    auto& buffer = context.buffer();\n    auto pos = buffer.iterator_at(selection.cursor());\n\n    auto res = find_surrounding(buffer, pos, opening, closing, flags, level);\n\n    // If the ends we're changing didn't move, find the parent\n    if (res and not (flags & ObjectFlags::Inner) and\n        (res->first.coord() == selection.min() or not (flags & ObjectFlags::ToBegin)) and\n        (res->second.coord() == selection.max() or not (flags & ObjectFlags::ToEnd)))\n        res = find_surrounding(buffer, pos, opening, closing, flags, level+1);\n\n    if (res)\n        return (flags & ObjectFlags::ToEnd) ? utf8_range(res->first, res->second)\n                                            : utf8_range(res->second, res->first);\n    return {};\n}\n\nOptional<Selection>\nselect_to(const Context& context, const Selection& selection,\n          Codepoint c, int count, bool inclusive)\n{\n    auto& buffer = context.buffer();\n    Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer};\n    Utf8Iterator end = begin;\n    do\n    {\n        ++end;\n        skip_while(end, buffer.end(), [c](Codepoint cur) { return cur != c; });\n        if (end == buffer.end())\n            return {};\n    }\n    while (--count > 0);\n\n    return utf8_range(begin, inclusive ? end : end-1);\n}\n\nOptional<Selection>\nselect_to_reverse(const Context& context, const Selection& selection,\n                  Codepoint c, int count, bool inclusive)\n{\n    auto& buffer = context.buffer();\n\n    // if we are selecting backwards from the beginning of the buffer,\n    // there is nothing more that can be selected.\n    if (selection.cursor() == buffer.begin())\n        return {};\n\n    Utf8Iterator begin{buffer.iterator_at(selection.cursor()), buffer};\n    Utf8Iterator end = begin;\n    do\n    {\n        --end;\n        if (skip_while_reverse(end, buffer.begin(),\n                               [c](Codepoint cur) { return cur != c; }))\n            return {};\n    }\n    while (--count > 0);\n\n    return utf8_range(begin, inclusive ? end : end+1);\n}\n\nOptional<Selection>\nselect_number(const Context& context, const Selection& selection,\n              int count, ObjectFlags flags)\n{\n    auto is_number = [&](char c) {\n        return (c >= '0' and c <= '9') or\n               (not (flags & ObjectFlags::Inner) and c == '.');\n    };\n\n    auto& buffer = context.buffer();\n    BufferIterator first = buffer.iterator_at(selection.cursor());\n    BufferIterator last = first;\n\n    if (not is_number(*first) and *first != '-')\n        return {};\n\n    if (flags & ObjectFlags::ToBegin)\n    {\n        skip_while_reverse(first, buffer.begin(), is_number);\n        if (not is_number(*first) and *first != '-' and\n            first+1 != buffer.end())\n            ++first;\n    }\n\n    if (flags & ObjectFlags::ToEnd)\n    {\n        if (*last == '-')\n            ++last;\n        skip_while(last, buffer.end(), is_number);\n        if (last != buffer.begin())\n            --last;\n    }\n\n    return (flags & ObjectFlags::ToEnd) ? Selection{first.coord(), last.coord()}\n                                        : Selection{last.coord(), first.coord()};\n}\n\nOptional<Selection>\nselect_sentence(const Context& context, const Selection& selection,\n                int count, ObjectFlags flags)\n{\n    auto is_end_of_sentence = [](char c) {\n        return c == '.' or c == ';' or c == '!' or c == '?';\n    };\n\n    auto& buffer = context.buffer();\n    BufferIterator first = buffer.iterator_at(selection.cursor());\n    BufferIterator last;\n\n    for (int i = 0; i <= count; ++i)\n    {\n        if (not (flags & ObjectFlags::ToEnd) and first != buffer.begin())\n        {\n            BufferIterator prev_non_blank = first-1;\n            skip_while_reverse(prev_non_blank, buffer.begin(),\n                               [](char c) { return is_horizontal_blank(c) or is_eol(c); });\n            if (is_end_of_sentence(*prev_non_blank))\n                first = prev_non_blank;\n        }\n\n        if (i == 0)\n            last = first;\n\n        if (flags & ObjectFlags::ToBegin)\n        {\n            bool saw_non_blank = false;\n            while (first != buffer.begin())\n            {\n                char cur = *first;\n                char prev = *(first-1);\n                if (not is_horizontal_blank(cur))\n                    saw_non_blank = true;\n                if (is_eol(prev) and is_eol(cur) and first + 1 != buffer.end())\n                {\n                    ++first;\n                    break;\n                }\n                else if (is_end_of_sentence(prev))\n                {\n                    if (saw_non_blank)\n                        break;\n                    else if (flags & ObjectFlags::ToEnd)\n                        last = first-1;\n                }\n                --first;\n            }\n            skip_while(first, buffer.end(), is_horizontal_blank);\n        }\n        if (flags & ObjectFlags::ToEnd)\n        {\n            while (last != buffer.end())\n            {\n                char cur = *last;\n                if (is_end_of_sentence(cur) or\n                    (is_eol(cur) and (last+1 == buffer.end() or is_eol(*(last+1)))))\n                    break;\n                ++last;\n            }\n            if (not (flags & ObjectFlags::Inner) and last != buffer.end())\n            {\n                ++last;\n                skip_while(last, buffer.end(), is_horizontal_blank);\n                --last;\n            }\n        }\n    }\n    return (flags & ObjectFlags::ToEnd) ? Selection{first.coord(), last.coord()}\n                                        : Selection{last.coord(), first.coord()};\n}\n\nOptional<Selection>\nselect_paragraph(const Context& context, const Selection& selection,\n                 int count, ObjectFlags flags)\n{\n    auto& buffer = context.buffer();\n    BufferIterator first = buffer.iterator_at(selection.cursor());\n    BufferIterator last;\n\n    for (int i = 0; i <= count; ++i)\n    {\n        if (not (flags & ObjectFlags::ToEnd) and first.coord() > BufferCoord{0,1} and\n            is_eol(*(first-1)) and first-1 != buffer.begin() and is_eol(*(first-2)))\n            --first;\n        else if ((flags & ObjectFlags::ToEnd) and\n                 first != buffer.begin() and (first+1) != buffer.end() and\n                 is_eol(*(first-1)) and is_eol(*first))\n            ++first;\n        if (i == 0)\n            last = first;\n\n        if ((flags & ObjectFlags::ToBegin) and first != buffer.begin())\n        {\n            skip_while_reverse(first, buffer.begin(),\n                               [](Codepoint c){ return is_eol(c); });\n            if (flags & ObjectFlags::ToEnd)\n                last = first;\n            while (first != buffer.begin())\n            {\n                char cur = *first;\n                char prev = *(first-1);\n                if (is_eol(prev) and is_eol(cur))\n                {\n                    ++first;\n                    break;\n                }\n                --first;\n            }\n        }\n        if (flags & ObjectFlags::ToEnd)\n        {\n            if (last != buffer.end() and is_eol(*last))\n                ++last;\n            while (last != buffer.end())\n            {\n                if (last != buffer.begin() and is_eol(*last) and is_eol(*(last-1)))\n                {\n                    if (not (flags & ObjectFlags::Inner))\n                        skip_while(last, buffer.end(),\n                                   [](Codepoint c){ return is_eol(c); });\n                    break;\n                }\n                ++last;\n            }\n            --last;\n        }\n    }\n    return (flags & ObjectFlags::ToEnd) ? Selection{first.coord(), last.coord()}\n                                        : Selection{last.coord(), first.coord()};\n}\n\nOptional<Selection>\nselect_whitespaces(const Context& context, const Selection& selection,\n                   int count, ObjectFlags flags)\n{\n    auto is_whitespace = [&](char c) {\n        return c == ' ' or c == '\\t' or\n            (not (flags & ObjectFlags::Inner) and c == '\\n');\n    };\n    auto& buffer = context.buffer();\n    BufferIterator first = buffer.iterator_at(selection.cursor());\n    BufferIterator last  = first;\n\n    if (not is_whitespace(*first))\n        return {};\n\n    if (flags & ObjectFlags::ToBegin)\n    {\n        if (is_whitespace(*first))\n        {\n            skip_while_reverse(first, buffer.begin(), is_whitespace);\n            if (not is_whitespace(*first))\n                ++first;\n        }\n    }\n    if (flags & ObjectFlags::ToEnd)\n    {\n        if (is_whitespace(*last))\n        {\n            skip_while(last, buffer.end(), is_whitespace);\n            --last;\n        }\n    }\n    return (flags & ObjectFlags::ToEnd) ? Selection{first.coord(), last.coord()}\n                                        : Selection{last.coord(), first.coord()};\n}\n\nOptional<Selection>\nselect_indent(const Context& context, const Selection& selection,\n              int count, ObjectFlags flags)\n{\n    auto get_indent = [](StringView str, int tabstop) {\n        CharCount indent = 0;\n        for (auto& c : str)\n        {\n            if (c == ' ')\n                ++indent;\n            else if (c =='\\t')\n                indent = (indent / tabstop + 1) * tabstop;\n            else\n                break;\n        }\n        return indent;\n    };\n\n    auto get_current_indent = [&](const Buffer& buffer, LineCount line, int tabstop)\n    {\n        for (auto l = line; l >= 0; --l)\n            if (buffer[l] != \"\\n\"_sv)\n                return get_indent(buffer[l], tabstop);\n        for (auto l = line+1; l < buffer.line_count(); ++l)\n            if (buffer[l] != \"\\n\"_sv)\n                return get_indent(buffer[l], tabstop);\n        return 0_char;\n    };\n\n    auto is_only_whitespaces = [](StringView str) {\n        auto it = str.begin();\n        skip_while(it, str.end(),\n                   [](char c){ return c == ' ' or c == '\\t' or c == '\\n'; });\n        return it == str.end();\n    };\n\n    const bool to_begin = flags & ObjectFlags::ToBegin;\n    const bool to_end   = flags & ObjectFlags::ToEnd;\n\n    auto& buffer = context.buffer();\n    int tabstop = context.options()[\"tabstop\"].get<int>();\n    auto pos = selection.cursor();\n    const LineCount line = pos.line;\n    const auto indent = get_current_indent(buffer, line, tabstop);\n\n    LineCount begin_line = line - 1;\n    if (to_begin)\n    {\n        while (begin_line >= 0 and (buffer[begin_line] == \"\\n\"_sv or\n                                    get_indent(buffer[begin_line], tabstop) >= indent))\n            --begin_line;\n    }\n    ++begin_line;\n    LineCount end_line = line + 1;\n    if (to_end)\n    {\n        const LineCount end = buffer.line_count();\n        while (end_line < end and (buffer[end_line] == \"\\n\"_sv or\n                                   get_indent(buffer[end_line], tabstop) >= indent))\n            ++end_line;\n    }\n    --end_line;\n    // remove only whitespaces lines in inner mode\n    if (flags & ObjectFlags::Inner)\n    {\n        while (begin_line < end_line and\n               is_only_whitespaces(buffer[begin_line]))\n            ++begin_line;\n        while (begin_line < end_line and\n               is_only_whitespaces(buffer[end_line]))\n            --end_line;\n    }\n\n    auto first = to_begin ? begin_line : pos;\n    auto last = to_end ? BufferCoord{end_line, buffer[end_line].length() - 1} : pos;\n    return to_end ? Selection{first, last} : Selection{last, first};\n}\n\nOptional<Selection>\nselect_argument(const Context& context, const Selection& selection,\n                int level, ObjectFlags flags)\n{\n    enum Class { None, Opening, Closing, Delimiter };\n    auto classify = [](Codepoint c) {\n        switch (c)\n        {\n        case '(': case '[': case '{': return Opening;\n        case ')': case ']': case '}': return Closing;\n        case ',': case ';': return Delimiter;\n        default: return None;\n        }\n    };\n\n    auto& buffer = context.buffer();\n    BufferIterator pos = buffer.iterator_at(selection.cursor());\n    switch (classify(*pos))\n    {\n        //case Closing: if (pos+1 != buffer.end()) ++pos; break;\n        case Opening:\n        case Delimiter: if (pos != buffer.begin()) --pos; break;\n        default: break;\n    };\n\n    bool first_arg = false;\n    BufferIterator begin = pos;\n    for (int lev = level; begin != buffer.begin(); --begin)\n    {\n        Class c = classify(*begin);\n        if (c == Closing)\n            ++lev;\n        else if (c == Opening and (lev-- == 0))\n        {\n            first_arg = true;\n            ++begin;\n            break;\n        }\n        else if (c == Delimiter and lev == 0)\n        {\n            ++begin;\n            break;\n        }\n    }\n\n    bool last_arg = false;\n    BufferIterator end = pos;\n    for (int lev = level; end != buffer.end(); ++end)\n    {\n        Class c = classify(*end);\n        if (c == Opening)\n            ++lev;\n        else if (end != pos and c == Closing and (lev-- == 0))\n        {\n            last_arg = true;\n            --end;\n            break;\n        }\n        else if (c == Delimiter and lev == 0)\n        {\n            // include whitespaces *after* the delimiter only for first argument\n            if (first_arg and not (flags & ObjectFlags::Inner))\n            {\n                while (end + 1 != buffer.end() and is_blank(*(end+1)))\n                    ++end;\n            }\n            break;\n        }\n    }\n\n    if (flags & ObjectFlags::Inner)\n    {\n        if (not last_arg)\n            --end;\n        skip_while(begin, end, is_blank);\n        skip_while_reverse(end, begin, is_blank);\n    }\n    // get starting delimiter for non inner last arg\n    else if (not first_arg and last_arg and begin != buffer.begin())\n        --begin;\n\n    if (end == buffer.end())\n        --end;\n\n    if (flags & ObjectFlags::ToBegin and not (flags & ObjectFlags::ToEnd))\n        return Selection{pos.coord(), begin.coord()};\n    return Selection{(flags & ObjectFlags::ToBegin ? begin : pos).coord(),\n                     end.coord()};\n}\n\nVector<Selection> for_each_sel(const Context& context, auto&& callback)\n{\n    auto& buffer = context.buffer();\n    Vector<Selection> res;\n    for (auto& sel : context.selections())\n        callback(res, buffer.iterator_at(sel.min()), buffer.iterator_at(buffer.char_next(sel.max())));\n    if (res.empty())\n        throw runtime_error(\"nothing selected\");\n    return res;\n}\n\ntemplate<WordType word_type>\nVector<Selection> select_nested_words(const Context& context, int count, ObjectFlags flags)\n{\n    auto is_word = [extra_word_chars=get_extra_word_chars(context)](Codepoint c) { return Kakoune::is_word<word_type>(c, extra_word_chars); };\n    auto is_not_word = [&](Codepoint c) { return not is_word(c); };\n\n    return for_each_sel(context, [&](Vector<Selection>& res, BufferIterator beg, BufferIterator end) {\n        for (auto it = Utf8Iterator{beg, context.buffer()}; it != end; )\n        {\n            if (not skip_while(it, end, is_not_word))\n                break;\n            auto start = it;\n            skip_while(it, end, is_word);\n            if (not (flags & ObjectFlags::Inner))\n                skip_while(it, end, is_horizontal_blank);\n            res.push_back({start.base().coord(), context.buffer().char_prev(it.base().coord())});\n        }\n    });\n}\ntemplate Vector<Selection> select_nested_words<Word>(const Context& context, int count, ObjectFlags flags);\ntemplate Vector<Selection> select_nested_words<WORD>(const Context& context, int count, ObjectFlags flags);\n\nVector<Selection> select_nested_numbers(const Context& context, int count, ObjectFlags flags)\n{\n    auto is_digit = [](char c) { return c >= '0' and c <= '9'; };\n    auto is_number = [&](char c) { return is_digit(c) or (not (flags & ObjectFlags::Inner) and c == '.'); };\n\n    return for_each_sel(context, [&](Vector<Selection>& res, BufferIterator it, BufferIterator end) {\n        while (it != end)\n        {\n            if (not skip_while(it, end, [&](char c) { return not (c == '-' or is_number(c)); }))\n                break;\n\n            auto start = it;\n            if (*it == '-')\n                ++it;\n            skip_while(it, end, is_number);\n            if (it == start)\n                ++it;\n            else if (std::find_if(start, it, is_digit) != it)\n                res.push_back({start.coord(), context.buffer().char_prev(it.coord())});\n        }\n    });\n}\n\nVector<Selection> select_nested_sentences(const Context& context, int count, ObjectFlags flags)\n{\n    auto is_end_of_sentence = [](char c) { return c == '.' or c == ';' or c == '!' or c == '?'; };\n\n    return for_each_sel(context, [&](Vector<Selection>& res, BufferIterator it, BufferIterator end) {\n        while (it != end)\n        {\n            if (not skip_while(it, end, [](char c) { return c == ' ' or c == '\\n' or c == '\\t'; }))\n                break;\n            auto start = it;\n            it = std::find_if(it, end, is_end_of_sentence);\n            ++it;\n            if (not (flags & ObjectFlags::Inner))\n                skip_while(it, end, [](char c) { return c == ' '; });\n            res.push_back({start.coord(), context.buffer().char_prev(it.coord())});\n        }\n    });\n}\n\nVector<Selection> select_nested_paragraphs(const Context& context, int count, ObjectFlags flags)\n{\n    auto is_eol = [](char c) { return c == '\\n'; };\n\n    return for_each_sel(context, [&](Vector<Selection>& res, BufferIterator it, BufferIterator end) {\n        while (it != end)\n        {\n            auto start = std::find_if(it, end, [&](char c) {return not is_eol(c); });\n            int consecutive_eols = 0;\n            for  (it = start; it != end; ++it)\n            {\n                if (is_eol(*it))\n                {\n                    ++consecutive_eols;\n                    if (consecutive_eols == 2 and (flags & ObjectFlags::Inner))\n                        break;\n                }\n                else if (consecutive_eols >= 2)\n                    break;\n                else\n                    consecutive_eols = 0;\n            }\n            res.push_back({start.coord(), context.buffer().char_prev(it.coord())});\n        }\n    });\n}\n\nVector<Selection> select_nested_whitespaces(const Context& context, int count, ObjectFlags flags)\n{\n    auto is_whitespace = [&](char c) { return c == ' ' or c == '\\t' or (not (flags & ObjectFlags::Inner) and c == '\\n'); };\n\n    return for_each_sel(context, [&](Vector<Selection>& res, BufferIterator it, BufferIterator end) {\n        while (it != end)\n        {\n            if (not skip_while(it, end, [&](char c) { return not is_whitespace(c); }))\n                break;\n            auto start = it;\n            skip_while(it, end, is_whitespace);\n            res.push_back({start.coord(), context.buffer().char_prev(it.coord())});\n        }\n    });\n}\n\nVector<Selection> select_nested_indents(const Context& context, int count, ObjectFlags flags)\n{\n    throw runtime_error(\"nested indents are not implemented\");\n}\n\nVector<Selection> select_nested_arguments(const Context& context, int count, ObjectFlags flags)\n{\n    auto is_whitespace = [&](char c) { return c == ' ' or c == '\\t' or c == '\\n'; };\n    const bool inner = (flags & ObjectFlags::Inner);\n    auto& buffer = context.buffer();\n\n    return for_each_sel(context, [&](Vector<Selection>& res, BufferIterator it, BufferIterator end) {\n        auto start = it;\n        int level = 0;\n        while (it != end)\n        {\n            switch(*it)\n            {\n                case '(': case '[': case '{': ++level; ++it; break;\n                case ')': case ']': case '}': --level; ++it; break;\n                case ',': case ';':\n                    if (level == 0)\n                    {\n                        res.push_back({start.coord(), inner ? buffer.char_prev(it.coord()) : it.coord()});\n                        ++it;\n                        while (inner and it != end and is_whitespace(*it))\n                            ++it;\n                        start = it;\n                        break;\n                    }\n                    [[fallthrough]];\n                default: ++it; break;\n            }\n        }\n        if (start != it)\n            res.push_back({start.coord(), context.buffer().char_prev(it.coord())});\n    });\n}\n\nVector<Selection> regex_select_nested(const Context& context, const Regex& opening, const Regex& closing, int count, ObjectFlags flags)\n{\n    kak_assert(opening != closing);\n    auto& buffer = context.buffer();\n    const bool inner = (flags & ObjectFlags::Inner);\n\n    return for_each_sel(context, [&](Vector<Selection>& res, BufferIterator beg, BufferIterator end) {\n        RegexIterator opening_matches{beg, end, buffer.begin(), buffer.end(), opening, RegexExecFlags::None, EventManager::handle_urgent_events};\n        RegexIterator closing_matches{beg, end, buffer.begin(), buffer.end(), closing, RegexExecFlags::None, EventManager::handle_urgent_events};\n        auto opening_it = opening_matches.begin(), closing_it = closing_matches.begin();\n        auto opening_end = opening_matches.end(),  closing_end = closing_matches.end();\n\n        Optional<BufferIterator> start;\n        int level = -count - 1;\n        while (opening_it != opening_end and closing_it != closing_end)\n        {\n            while (opening_it != opening_end and (closing_it == closing_end or (*opening_it)[0].first < (*closing_it)[0].first))\n            {\n                if (++level == 0)\n                    start = inner ? (*opening_it)[0].second : (*opening_it)[0].first;\n                ++opening_it;\n            }\n            while (closing_it != closing_end and (opening_it == opening_end or (*closing_it)[0].first < (*opening_it)[0].first))\n            {\n                if (level-- == 0 and start)\n                {\n                    auto end_coord = buffer.char_prev((inner ? (*closing_it)[0].first : (*closing_it)[0].second).coord());\n                    if (start->coord() <= end_coord)\n                        res.push_back({start->coord(), end_coord});\n                    start.reset();\n                }\n                ++closing_it;\n            }\n        }\n        if (start)\n            res.push_back({start->coord(), buffer.char_prev(end.coord())});\n    });\n}\n\nVector<Selection> regex_select_nested(const Context& context, const Regex& delimiter, ObjectFlags flags)\n{\n    auto& buffer = context.buffer();\n    const bool inner = (flags & ObjectFlags::Inner);\n\n    return for_each_sel(context, [&](Vector<Selection>& res, BufferIterator beg, BufferIterator end) {\n        Optional<BufferIterator> start;\n        for (auto m : RegexIterator{beg, end, buffer.begin(), buffer.end(),\n                                    delimiter, RegexExecFlags::None, EventManager::handle_urgent_events})\n        {\n            if (not start)\n                start = inner ? m[0].second : m[0].first;\n            else\n            {\n                auto end_coord = buffer.char_prev((inner ? m[0].first : m[0].second).coord());\n                if (start->coord() <= end_coord)\n                    res.push_back({start->coord(), end_coord});\n                start.reset();\n            }\n        }\n        if (start)\n            res.push_back({start->coord(), buffer.char_prev(end.coord())});\n    });\n}\n\nOptional<Selection>\nselect_lines(const Context& context, const Selection& selection)\n{\n    auto& buffer = context.buffer();\n    BufferCoord anchor = selection.anchor();\n    BufferCoord cursor  = selection.cursor();\n    BufferCoord& to_line_start = anchor <= cursor ? anchor : cursor;\n    BufferCoord& to_line_end = anchor <= cursor ? cursor : anchor;\n\n    to_line_start.column = 0;\n    to_line_end.column = buffer[to_line_end.line].length()-1;\n\n    return Selection{anchor, {cursor, max_column}};\n}\n\nOptional<Selection>\ntrim_partial_lines(const Context& context, const Selection& selection)\n{\n    auto& buffer = context.buffer();\n    BufferCoord anchor = selection.anchor();\n    BufferCoord cursor  = selection.cursor();\n    BufferCoord& to_line_start = anchor <= cursor ? anchor : cursor;\n    BufferCoord& to_line_end = anchor <= cursor ? cursor : anchor;\n\n    if (to_line_start.column != 0)\n        to_line_start = to_line_start.line+1;\n    if (to_line_end.column != buffer[to_line_end.line].length()-1)\n    {\n        if (to_line_end.line == 0)\n            return {};\n\n        auto prev_line = to_line_end.line-1;\n        to_line_end = BufferCoord{ prev_line, buffer[prev_line].length()-1 };\n    }\n\n    if (to_line_start > to_line_end)\n        return {};\n\n    return Selection{anchor, {cursor, max_column}};\n}\n\nstatic RegexExecFlags\nmatch_flags(const Buffer& buf, const BufferIterator& begin, const BufferIterator& end)\n{\n    return match_flags(is_bol(begin.coord()), is_eol(buf, end.coord()),\n                       is_bow(buf, begin.coord()), is_eow(buf, end.coord()));\n}\n\nstatic bool find_next(const Buffer& buffer, const BufferIterator& pos,\n                      MatchResults<BufferIterator>& matches,\n                      const Regex& ex, bool& wrapped)\n{\n    if (pos != buffer.end() and\n        regex_search(pos, buffer.end(), buffer.begin(), buffer.end(),\n                     matches, ex, match_flags(buffer, pos, buffer.end()),\n                     EventManager::handle_urgent_events))\n        return true;\n    wrapped = true;\n    return regex_search(buffer.begin(), buffer.end(), buffer.begin(), buffer.end(),\n                        matches, ex, match_flags(buffer, buffer.begin(), buffer.end()),\n                        EventManager::handle_urgent_events);\n}\n\nstatic bool find_prev(const Buffer& buffer, const BufferIterator& pos,\n                      MatchResults<BufferIterator>& matches,\n                      const Regex& ex, bool& wrapped)\n{\n    if (pos != buffer.begin() and\n        backward_regex_search(buffer.begin(), pos, buffer.begin(), buffer.end(),\n                              matches, ex,\n                              match_flags(buffer, buffer.begin(), pos) |\n                              RegexExecFlags::NotInitialNull,\n                              EventManager::handle_urgent_events))\n        return true;\n    wrapped = true;\n    return backward_regex_search(buffer.begin(), buffer.end(), buffer.begin(), buffer.end(),\n                                 matches, ex,\n                                 match_flags(buffer, buffer.begin(), buffer.end()) |\n                                 RegexExecFlags::NotInitialNull,\n                                 EventManager::handle_urgent_events);\n}\n\nSelection find_next_match(const Context& context, const Selection& sel, const Regex& regex, RegexMode mode, bool& wrapped)\n{\n    kak_assert(is_direction(mode));\n    const bool forward = mode & RegexMode::Forward;\n    auto& buffer = context.buffer();\n    MatchResults<BufferIterator> matches;\n    auto pos = buffer.iterator_at(forward ? sel.max() : sel.min());\n    wrapped = false;\n    const bool found = forward ?\n        find_next(buffer, utf8::next(pos, buffer.end()), matches, regex, wrapped)\n      : find_prev(buffer, pos, matches, regex, wrapped);\n\n    if (not found or matches[0].first == buffer.end())\n        throw runtime_error(format(\"no matches found: '{}'\", regex.str()));\n\n    CaptureList captures;\n    for (const auto& match : matches)\n        captures.push_back(buffer.string(match.first.coord(), match.second.coord()));\n\n    auto begin = matches[0].first, end = matches[0].second;\n    end = (begin == end) ? end : utf8::previous(end, begin);\n    if (not forward)\n        std::swap(begin, end);\n\n    return {begin.coord(), end.coord(), std::move(captures)};\n}\n\nVector<Selection> select_matches(const Buffer& buffer, ConstArrayView<Selection> selections, const Regex& regex, int capture_idx)\n{\n    const int mark_count = (int)regex.mark_count();\n    if (capture_idx < 0 or capture_idx > mark_count)\n        throw runtime_error(\"invalid capture number\");\n\n    Vector<Selection> result;\n    ThreadedRegexVM<BufferIterator, RegexMode::Forward | RegexMode::Search> vm{*regex.impl()};\n    for (auto& sel : selections)\n    {\n        auto sel_beg = buffer.iterator_at(sel.min());\n        auto sel_end = utf8::next(buffer.iterator_at(sel.max()), buffer.end());\n\n        for (auto&& match : RegexIterator{sel_beg, sel_end, vm, match_flags(buffer, sel_beg, sel_end),\n                                          EventManager::handle_urgent_events})\n        {\n            auto capture = match[capture_idx];\n            if (not capture.matched or capture.first == sel_end)\n                continue;\n\n            CaptureList captures;\n            captures.reserve(mark_count);\n            for (const auto& submatch : match)\n                captures.push_back(buffer.string(submatch.first.coord(),\n                                                 submatch.second.coord()));\n\n            auto begin = capture.first, end = capture.second;\n            result.push_back(\n                keep_direction({ begin.coord(),\n                                 (begin == end ? end : utf8::previous(end, begin)).coord(),\n                                 std::move(captures) }, sel));\n        }\n    }\n    if (result.empty())\n        throw runtime_error(\"nothing selected\");\n\n    std::sort(result.begin(), result.end(), compare_selections);\n    return result;\n}\n\nVector<Selection> split_on_matches(const Buffer& buffer, ConstArrayView<Selection> selections, const Regex& regex, int capture_idx)\n{\n    if (capture_idx < 0 or capture_idx > (int)regex.mark_count())\n        throw runtime_error(\"invalid capture number\");\n\n    Vector<Selection> result;\n    auto buf_end = buffer.end();\n    ThreadedRegexVM<BufferIterator, RegexMode::Forward | RegexMode::Search> vm{*regex.impl()};\n    for (auto& sel : selections)\n    {\n        auto sel_begin = buffer.iterator_at(sel.min());\n        auto begin = sel_begin;\n        auto sel_end = utf8::next(buffer.iterator_at(sel.max()), buf_end);\n\n        for (auto&& match : RegexIterator{begin, sel_end, vm, match_flags(buffer, begin, sel_end)})\n        {\n            auto capture = match[capture_idx];\n            BufferIterator end = capture.first;\n            if (end == buf_end)\n                continue;\n\n            if (end != sel_begin)\n            {\n                auto sel_end = (begin == end) ? end : utf8::previous(end, begin);\n                result.push_back(keep_direction({ begin.coord(), sel_end.coord() }, sel));\n            }\n            begin = capture.second;\n        }\n        if (begin.coord() <= sel.max())\n            result.push_back(keep_direction({ begin.coord(), sel.max() }, sel));\n    }\n    if (result.empty())\n        throw runtime_error(\"nothing selected\");\n    return result;\n}\n\nUnitTest test_find_surrounding{[]()\n{\n    StringView s = \"{foo [bar { baz[] }]}\";\n    auto check_equal = [&](const char* pos, StringView opening, StringView closing,\n                           ObjectFlags flags, int level, StringView expected) {\n        auto res = find_surrounding(s, pos,\n                                    Regex{\"\\\\Q\" + opening, RegexCompileFlags::Backward},\n                                    Regex{\"\\\\Q\" + closing, RegexCompileFlags::Backward},\n                                    flags, level);\n        kak_assert((not res) == expected.empty());\n        if (not res)\n            return;\n        auto min = std::min(res->first, res->second),\n             max = std::max(res->first, res->second);\n        kak_assert(StringView{min, max+1} == expected);\n    };\n\n    check_equal(s.begin() + 13, '{', '}', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, \"{ baz[] }\");\n    check_equal(s.begin() + 13, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd | ObjectFlags::Inner, 0, \"bar { baz[] }\");\n    check_equal(s.begin() + 5, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, \"[bar { baz[] }]\");\n    check_equal(s.begin() + 10, '{', '}', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, \"{ baz[] }\");\n    check_equal(s.begin() + 16, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd | ObjectFlags::Inner, 0, \"\");\n    check_equal(s.begin() + 18, '[', ']', ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, \"[bar { baz[] }]\");\n    check_equal(s.begin() + 6, '[', ']', ObjectFlags::ToBegin, 0, \"[b\");\n\n    s = \"[*][] foo\";\n    kak_assert(not find_surrounding(s, s.begin() + 6,\n                                    Regex{\"\\\\Q[\", RegexCompileFlags::Backward},\n                                    Regex{\"\\\\Q]\", RegexCompileFlags::Backward},\n                                    ObjectFlags::ToBegin, 0));\n\n    s = \"begin foo begin bar end end\";\n    check_equal(s.begin() + 6, \"begin\", \"end\", ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, s);\n    check_equal(s.begin() + 22, \"begin\", \"end\", ObjectFlags::ToBegin | ObjectFlags::ToEnd, 0, \"begin bar end\");\n}};\n\n}\n"
  },
  {
    "path": "src/selectors.hh",
    "content": "#ifndef selectors_hh_INCLUDED\n#define selectors_hh_INCLUDED\n\n#include \"enum.hh\"\n#include \"optional.hh\"\n#include \"meta.hh\"\n#include \"unicode.hh\"\n#include \"vector.hh\"\n#include \"array.hh\"\n\nnamespace Kakoune\n{\n\nstruct Selection;\nclass Buffer;\nclass Regex;\nclass Context;\n\nSelection keep_direction(Selection res, const Selection& ref);\n\ntemplate<WordType word_type>\nOptional<Selection>\nselect_to_next_word(const Context& context, const Selection& selection);\n\ntemplate<WordType word_type>\nOptional<Selection>\nselect_to_next_word_end(const Context& context, const Selection& selection);\n\ntemplate<WordType word_type>\nOptional<Selection>\nselect_to_previous_word(const Context& context, const Selection& selection);\n\nOptional<Selection>\nselect_line(const Context& context, const Selection& selection);\n\ntemplate<bool forward>\nOptional<Selection>\nselect_matching(const Context& context, const Selection& selection);\n\nOptional<Selection>\nselect_to(const Context& context, const Selection& selection,\n                    Codepoint c, int count, bool inclusive);\nOptional<Selection>\nselect_to_reverse(const Context& context, const Selection& selection,\n                  Codepoint c, int count, bool inclusive);\n\ntemplate<bool only_move>\nOptional<Selection>\nselect_to_line_end(const Context& context, const Selection& selection);\n\ntemplate<bool only_move>\nOptional<Selection>\nselect_to_line_begin(const Context& context, const Selection& selection);\n\nOptional<Selection>\nselect_to_first_non_blank(const Context& context, const Selection& selection);\n\nenum class ObjectFlags\n{\n    ToBegin = 1,\n    ToEnd   = 2,\n    Inner   = 4,\n    Nested  = 8,\n};\n\nconstexpr bool with_bit_ops(Meta::Type<ObjectFlags>) { return true; }\n\nconstexpr auto enum_desc(Meta::Type<ObjectFlags>)\n{\n    return make_array<EnumDesc<ObjectFlags>>({\n        { ObjectFlags::ToBegin, \"to_begin\" },\n        { ObjectFlags::ToEnd, \"to_end\" },\n        { ObjectFlags::Inner, \"inner\" },\n    });\n}\n\ntemplate<WordType word_type>\nOptional<Selection> select_word(const Context& context, const Selection& selection, int count, ObjectFlags flags);\nOptional<Selection> select_number(const Context& context, const Selection& selection, int count, ObjectFlags flags);\nOptional<Selection> select_sentence(const Context& context, const Selection& selection, int count, ObjectFlags flags);\nOptional<Selection> select_paragraph(const Context& context, const Selection& selection, int count, ObjectFlags flags);\nOptional<Selection> select_whitespaces(const Context& context, const Selection& selection, int count, ObjectFlags flags);\nOptional<Selection> select_indent(const Context& context, const Selection& selection, int count, ObjectFlags flags);\nOptional<Selection> select_argument(const Context& context, const Selection& selection, int level, ObjectFlags flags);\n\ntemplate<WordType word_type>\nVector<Selection, MemoryDomain::Selections> select_nested_words(const Context& context, int count, ObjectFlags flags);\nVector<Selection, MemoryDomain::Selections> select_nested_numbers(const Context& context, int count, ObjectFlags flags);\nVector<Selection, MemoryDomain::Selections> select_nested_sentences(const Context& context, int count, ObjectFlags flags);\nVector<Selection, MemoryDomain::Selections> select_nested_paragraphs(const Context& context, int count, ObjectFlags flags);\nVector<Selection, MemoryDomain::Selections> select_nested_whitespaces(const Context& context, int count, ObjectFlags flags);\nVector<Selection, MemoryDomain::Selections> select_nested_indents(const Context& context, int count, ObjectFlags flags);\nVector<Selection, MemoryDomain::Selections> select_nested_arguments(const Context& context, int count, ObjectFlags flags);\nVector<Selection, MemoryDomain::Selections> regex_select_nested(const Context& context, const Regex& opening, const Regex& closing, int level, ObjectFlags flags);\nVector<Selection, MemoryDomain::Selections> regex_select_nested(const Context& context, const Regex& delimiter, ObjectFlags flags);\n\nOptional<Selection> select_lines(const Context& context, const Selection& selection);\nOptional<Selection> trim_partial_lines(const Context& context, const Selection& selection);\n\nenum class RegexMode;\n\nSelection find_next_match(const Context& context, const Selection& sel,\n                          const Regex& regex, RegexMode mode, bool& wrapped);\n\nVector<Selection, MemoryDomain::Selections>\nselect_matches(const Buffer& buffer, ConstArrayView<Selection> selections,\n               const Regex& regex, int capture_idx = 0);\n\nVector<Selection, MemoryDomain::Selections>\nsplit_on_matches(const Buffer& buffer, ConstArrayView<Selection> selections,\n                 const Regex& regex, int capture_idx = 0);\n\nOptional<Selection>\nselect_surrounding(const Context& context, const Selection& selection,\n                   const Regex& opening, const Regex& closing, int level,\n                   ObjectFlags flags);\n\n}\n\n#endif // selectors_hh_INCLUDED\n"
  },
  {
    "path": "src/shared_string.cc",
    "content": "#include \"shared_string.hh\"\n#include \"debug.hh\"\n#include \"format.hh\"\n\nnamespace Kakoune\n{\n\nStringDataPtr StringData::Registry::intern(StringView str, size_t hash)\n{\n    kak_assert(hash_value(str) == hash);\n    auto index = m_strings.find_index(str, hash);\n    if (index >= 0)\n        return StringDataPtr{m_strings.item(index).value};\n\n    auto data = StringData::create(str);\n    data->refcount |= interned_flag;\n    m_strings.insert({data->strview(), data.get()}, hash);\n    return data;\n}\n\nStringDataPtr StringData::Registry::intern(StringView str)\n{\n    return intern(str, hash_value(str));\n}\n\nvoid StringData::Registry::remove(StringView str)\n{\n    kak_assert(m_strings.contains(str));\n    m_strings.unordered_remove(str);\n}\n\nvoid StringData::Registry::debug_stats() const\n{\n    write_to_debug_buffer(\"Interned Strings stats:\");\n    size_t count = m_strings.size();\n    size_t total_refcount = 0;\n    size_t total_size = 0;\n    for (auto& st : m_strings)\n    {\n        total_refcount += st.value->refcount & refcount_mask;\n        total_size += (int)st.value->length;\n    }\n    write_to_debug_buffer(format(\"  count: {}\", count));\n    write_to_debug_buffer(format(\"  data size: {}, mean: {}\", total_size, (float)total_size/count));\n    write_to_debug_buffer(format(\"  refcounts: {}, mean: {}\", total_refcount, (float)total_refcount/count));\n}\n\n}\n"
  },
  {
    "path": "src/shared_string.hh",
    "content": "#ifndef shared_string_hh_INCLUDED\n#define shared_string_hh_INCLUDED\n\n#include \"string.hh\"\n#include \"ref_ptr.hh\"\n#include \"utils.hh\"\n#include \"hash_map.hh\"\n\n#include <cstring>\n\nnamespace Kakoune\n{\n\nstruct StringData : UseMemoryDomain<MemoryDomain::SharedString>\n{\n    uint32_t refcount;\n    const int length;\n\n    [[gnu::always_inline]]\n    const char* data() const { return reinterpret_cast<const char*>(this + 1); }\n    [[gnu::always_inline]]\n    StringView strview() const { return {data(), length}; }\n\nprivate:\n    StringData(int len) : refcount(0), length(len) {}\n\n    static constexpr uint32_t interned_flag = 1u << 31;\n    static constexpr uint32_t refcount_mask = ~interned_flag;\n\n    struct PtrPolicy\n    {\n        static void inc_ref(StringData* r, void*) noexcept { ++r->refcount; }\n        static void dec_ref(StringData* r, void*) noexcept\n        {\n            if ((--r->refcount & refcount_mask) > 0)\n                return;\n            if (r->refcount & interned_flag)\n                Registry::instance().remove(r->strview());\n            auto alloc_len = sizeof(StringData) + r->length + 1;\n            r->~StringData();\n            operator delete(r, alloc_len);\n        }\n        static void ptr_moved(StringData*, void*, void*) noexcept {}\n    };\n\npublic:\n    using Ptr = RefPtr<StringData, PtrPolicy>;\n\n    class Registry : public Singleton<Registry>\n    {\n    public:\n        void debug_stats() const;\n        Ptr intern(StringView str);\n        Ptr intern(StringView str, size_t hash);\n        void remove(StringView str);\n\n    private:\n        HashMap<StringView, StringData*, MemoryDomain::SharedString> m_strings;\n    };\n\n    static Ptr create(ConvertibleTo<StringView> auto&&... strs)\n    {\n        const int len = ((int)StringView{strs}.length() + ...);\n        void* ptr = operator new(sizeof(StringData) + len + 1);\n        auto* res = new (ptr) StringData(len);\n        auto* data = reinterpret_cast<char*>(res + 1);\n        auto append = [&](StringView str) {\n            if (str.empty()) // memcpy(..., nullptr, 0) is UB\n                return;\n            memcpy(data, str.begin(), (size_t)str.length());\n            data += (int)str.length();\n        };\n        (append(strs), ...);\n        *data = 0;\n        return Ptr{res};\n    }\n};\n\nusing StringDataPtr = StringData::Ptr;\nusing StringRegistry = StringData::Registry;\n\ninline StringDataPtr intern(StringView str) { return StringRegistry::instance().intern(str); }\ninline StringDataPtr intern(StringView str, size_t hash) { return StringRegistry::instance().intern(str, hash); }\n\n}\n\n#endif // shared_string_hh_INCLUDED\n"
  },
  {
    "path": "src/shell_manager.cc",
    "content": "#include \"shell_manager.hh\"\n\n#include \"debug.hh\"\n#include \"client.hh\"\n#include \"clock.hh\"\n#include \"context.hh\"\n#include \"command_manager.hh\"\n#include \"display_buffer.hh\"\n#include \"event_manager.hh\"\n#include \"face_registry.hh\"\n#include \"option_manager.hh\"\n#include \"file.hh\"\n#include \"flags.hh\"\n#include \"option_types.hh\"\n#include \"regex.hh\"\n\n#include <chrono>\n#include <cstring>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <cstdlib>\n#include <errno.h>\n\n#if defined(__CYGWIN__)\n#define vfork fork\n#endif\n\nextern char **environ;\n\nnamespace Kakoune\n{\n\nShellManager::ShellManager(ConstArrayView<EnvVarDesc> builtin_env_vars)\n    : m_env_vars{builtin_env_vars}\n{\n    auto is_executable = [](StringView path) {\n        struct stat st;\n        if (stat(path.zstr(), &st))\n            return false;\n\n        bool executable = (st.st_mode & S_IXUSR)\n                        | (st.st_mode & S_IXGRP)\n                        | (st.st_mode & S_IXOTH);\n        return S_ISREG(st.st_mode) and executable;\n    };\n\n    if (const char* shell = getenv(\"KAKOUNE_POSIX_SHELL\"))\n    {\n        if (not is_executable(shell))\n            throw runtime_error{format(\"KAKOUNE_POSIX_SHELL '{}' is not executable\", shell)};\n        m_shell = shell;\n    }\n    else // Get a guaranteed to be POSIX shell binary\n    {\n        #if defined(_CS_PATH)\n        auto size = confstr(_CS_PATH, nullptr, 0);\n        String path; path.resize(size-1, 0);\n        confstr(_CS_PATH, path.data(), size);\n        #else\n        StringView path = \"/bin:/usr/bin\";\n        #endif\n        for (auto dir : StringView{path} | split<StringView>(':'))\n        {\n            auto candidate = format(\"{}/sh\", dir);\n            if (is_executable(candidate))\n            {\n                m_shell = std::move(candidate);\n                break;\n            }\n        }\n        if (m_shell.empty())\n            throw runtime_error{format(\"unable to find a posix shell in {}\", path)};\n    }\n\n    // Add Kakoune binary location to the path to guarantee that %sh{ ... }\n    // have access to the kak command regardless of if the user installed it\n    {\n        const char* path = getenv(\"PATH\");\n        auto new_path = format(\"{}../libexec/kak:{}\", split_path(get_kak_binary_path()).first, path);\n        setenv(\"PATH\", new_path.c_str(), 1);\n    }\n}\n\nnamespace\n{\n\nShell spawn_shell(const char* shell, StringView cmdline,\n                  ConstArrayView<String> params,\n                  ConstArrayView<String> kak_env,\n                  bool open_stdin) noexcept\n{\n    Vector<const char*> envptrs;\n    for (char** envp = environ; *envp; ++envp)\n        envptrs.push_back(*envp);\n    for (auto& env : kak_env)\n        envptrs.push_back(env.c_str());\n    envptrs.push_back(nullptr);\n\n    auto cmdlinezstr = cmdline.zstr();\n    Vector<const char*> execparams = { \"sh\", \"-c\", cmdlinezstr };\n    if (not params.empty())\n        execparams.push_back(shell);\n    for (auto& param : params)\n        execparams.push_back(param.c_str());\n    execparams.push_back(nullptr);\n\n    auto make_pipe = []() -> Array<UniqueFd, 2> {\n        if (int pipefd[2] = {-1, -1}; ::pipe(pipefd) == 0)\n            return {UniqueFd{pipefd[0]}, UniqueFd{pipefd[1]}};\n        throw runtime_error(format(\"unable to create pipe, errno: {}\", ::strerror(errno)));\n    };\n\n    auto stdin_pipe = open_stdin ? make_pipe() : Array{UniqueFd{open(\"/dev/null\", O_RDONLY)}, UniqueFd{}};\n    auto stdout_pipe = make_pipe();\n    auto stderr_pipe = make_pipe();\n    if (pid_t pid = vfork())\n        return {pid, std::move(stdin_pipe[1]), std::move(stdout_pipe[0]), std::move(stderr_pipe[0])};\n\n    constexpr auto renamefd = [](int oldfd, int newfd) {\n        if (oldfd == newfd)\n            return;\n        dup2(oldfd, newfd);\n        close(oldfd);\n    };\n\n    renamefd((int)stdin_pipe[0], 0);\n    renamefd((int)stdout_pipe[1], 1);\n    renamefd((int)stderr_pipe[1], 2);\n\n    close((int)stdin_pipe[1]);\n    close((int)stdout_pipe[0]);\n    close((int)stderr_pipe[0]);\n\n    execve(shell, (char* const*)execparams.data(), (char* const*)envptrs.data());\n    char buffer[1024];\n    write(STDERR_FILENO, format_to(buffer, \"execve failed: {}\\n\", strerror(errno)));\n    _exit(-1);\n    return {-1, {}, {}, {}};\n}\n\ntemplate<typename GetValue>\nVector<String> generate_env(StringView cmdline, ConstArrayView<String> params, const Context& context, GetValue&& get_value)\n{\n    static const Regex re(R\"(\\bkak_(quoted_)?(\\w+)\\b)\");\n\n    Vector<String> env;\n    auto add_matches = [&](StringView s) {\n        for (auto&& match : RegexIterator{s.begin(), s.end(), re})\n        {\n            StringView name{match[2].first, match[2].second};\n            StringView shell_name{match[0].first, match[0].second};\n\n            auto match_name = [&](const String& s) {\n                return s.substr(0_byte, shell_name.length()) == shell_name and\n                       s.substr(shell_name.length(), 1_byte) == \"=\";\n            };\n            if (any_of(env, match_name))\n                continue;\n\n            try\n            {\n                StringView quoted{match[1].first, match[1].second};\n                Quoting quoting = match[1].matched ? Quoting::Shell : Quoting::Raw;\n                env.push_back(format(\"kak_{}{}={}\", quoted, name, get_value(name, quoting)));\n            } catch (runtime_error&) {}\n        }\n    };\n    add_matches(cmdline);\n    for (auto&& param : params)\n        add_matches(param);\n\n    return env;\n}\n\ntemplate<typename OnClose>\nFDWatcher make_reader(int fd, String& contents, OnClose&& on_close)\n{\n    return {fd, FdEvents::Read, EventMode::Urgent,\n            [&contents, on_close](FDWatcher& watcher, FdEvents, EventMode) {\n        const int fd = watcher.fd();\n        char buffer[1024];\n        while (fd_readable(fd))\n        {\n            ssize_t size = ::read(fd, buffer, sizeof(buffer));\n            if (size <= 0)\n            {\n                if (size < 0 and errno == EAGAIN)\n                    continue; // try again\n\n                watcher.disable();\n                on_close(size == 0);\n                return;\n            }\n            contents += StringView{buffer, buffer+size};\n        }\n    }};\n}\n\nFDWatcher make_pipe_writer(UniqueFd& fd, const FunctionRef<StringView ()>& generator)\n{\n    int flags = fcntl((int)fd, F_GETFL, 0);\n    fcntl((int)fd, F_SETFL, flags | O_NONBLOCK);\n    return {(int)fd, FdEvents::Write, EventMode::Urgent,\n            [&generator, &fd, contents=generator()](FDWatcher& watcher, FdEvents, EventMode) mutable {\n        while (fd_writable((int)fd))\n        {\n            ssize_t size = ::write((int)fd, contents.begin(),\n                                   (size_t)contents.length());\n            if (size > 0)\n            {\n                contents = contents.substr(ByteCount{(int)size});\n                if (contents.empty())\n                    contents = generator();\n            }\n            if (size == -1 and (errno == EAGAIN or errno == EWOULDBLOCK))\n                return;\n            if (size < 0 or contents.empty())\n            {\n                fd.close();\n                watcher.disable();\n                return;\n            }\n        }\n    }};\n}\n\nstruct CommandFifos\n{\n    String base_dir;\n    String command;\n    FDWatcher command_watcher;\n\n    CommandFifos(Context& context, const ShellContext& shell_context)\n      : base_dir(format(\"{}/kak-fifo.XXXXXX\", tmpdir())),\n        command_watcher([&] {\n            if (mkdtemp(base_dir.data()) == nullptr or\n                mkfifo(command_fifo_path().c_str(), 0600) != 0 or\n                mkfifo(response_fifo_path().c_str(), 0600) != 0)\n                throw runtime_error(format(\"unable to create command/response fifos, errno: {}\", ::strerror(errno)));\n\n            int fd = open(command_fifo_path().c_str(), O_RDONLY | O_NONBLOCK);\n            return make_reader(fd, command, [&, fd](bool graceful) {\n                if (not graceful)\n                {\n                    write_to_debug_buffer(format(\"error reading from command fifo '{}'\", strerror(errno)));\n                    return;\n                }\n                CommandManager::instance().execute(command, context, shell_context);\n                command.clear();\n                command_watcher.reset_fd(fd);\n            });\n        }())\n    {\n    }\n\n    ~CommandFifos()\n    {\n        command_watcher.close_fd();\n        unlink(command_fifo_path().c_str());\n        unlink(response_fifo_path().c_str());\n        rmdir(base_dir.c_str());\n    }\n\n    String command_fifo_path() const { return format(\"{}/command-fifo\", base_dir); }\n    String response_fifo_path() const { return format(\"{}/response-fifo\", base_dir); }\n};\n\n}\n\nstd::pair<String, int> ShellManager::eval(\n    StringView cmdline, const Context& context, FunctionRef<StringView ()> input_generator,\n    Flags flags, const ShellContext& shell_context)\n{\n    const DebugFlags debug_flags = context.options()[\"debug\"].get<DebugFlags>();\n    const bool profile = debug_flags & DebugFlags::Profile;\n    if (debug_flags & DebugFlags::Shell)\n        write_to_debug_buffer(format(\"shell:\\n{}\\n----\\nargs: {}\\n----\\n\", cmdline, join(shell_context.params | transform(shell_quote), ' ')));\n\n    auto start_time = profile ? Clock::now() : Clock::time_point{};\n\n    Optional<CommandFifos> command_fifos;\n\n    auto kak_env = generate_env(cmdline, shell_context.params, context, [&](StringView name, Quoting quoting) {\n        if (name == \"command_fifo\" or name == \"response_fifo\")\n        {\n            if (not command_fifos)\n                command_fifos.emplace(const_cast<Context&>(context), shell_context);\n            return name == \"command_fifo\" ?\n                command_fifos->command_fifo_path() : command_fifos->response_fifo_path();\n        }\n\n        if (auto it = shell_context.env_vars.find(name); it != shell_context.env_vars.end())\n            return it->value;\n        return join(get_val(name, context) | transform(quoter(quoting)), ' ', false);\n    });\n\n    auto spawn_time = profile ? Clock::now() : Clock::time_point{};\n    auto shell = spawn_shell(m_shell.c_str(), cmdline, shell_context.params, kak_env, true);\n    auto wait_time = Clock::now();\n\n    String stdout_contents, stderr_contents;\n    auto stdout_reader = make_reader((int)shell.out, stdout_contents, [&](bool){ shell.out.close(); });\n    auto stderr_reader = make_reader((int)shell.err, stderr_contents, [&](bool){ shell.err.close(); });\n    auto stdin_writer = make_pipe_writer(shell.in, input_generator);\n\n    // block SIGCHLD to make sure we wont receive it before\n    // our call to pselect, that will end up blocking indefinitly.\n    sigset_t mask, orig_mask;\n    sigemptyset(&mask);\n    sigaddset(&mask, SIGCHLD);\n    sigprocmask(SIG_BLOCK, &mask, &orig_mask);\n    auto restore_mask = OnScopeEnd([&] { sigprocmask(SIG_SETMASK, &orig_mask, nullptr); });\n\n    int status = 0;\n    // check for termination now that SIGCHLD is blocked\n    bool terminated = waitpid((int)shell.pid, &status, WNOHANG) != 0;\n    bool failed = false;\n\n    using namespace std::chrono;\n    BusyIndicator busy_indicator{context, [&](seconds elapsed) {\n        return DisplayLine{format(\"waiting for shell command to finish{} ({}s)\",\n                                  terminated ? \" (shell terminated)\" : \"\", elapsed.count()),\n                           context.faces()[failed ? \"Error\" : \"Information\"]};\n    }};\n\n    bool cancelling = false;\n    while (not terminated or shell.in or\n           ((flags & Flags::WaitForStdout) and (shell.out or shell.err)))\n    {\n        try\n        {\n            EventManager::instance().handle_next_events(EventMode::Urgent, &orig_mask);\n        }\n        catch (cancel&)\n        {\n            kill((int)shell.pid, SIGINT);\n            cancelling = true;\n        }\n        catch (runtime_error& error)\n        {\n            write_to_debug_buffer(format(\"error while waiting for shell: {}\", error.what()));\n            failed = true;\n        }\n        if (not terminated)\n            terminated = waitpid((int)shell.pid, &status, WNOHANG) == (int)shell.pid;\n    }\n\n    if (not stderr_contents.empty())\n        write_to_debug_buffer(format(\"shell stderr: <<<\\n{}>>>\", stderr_contents));\n\n    if (profile)\n    {\n        auto end_time = Clock::now();\n        auto full = duration_cast<microseconds>(end_time - start_time);\n        auto spawn = duration_cast<microseconds>(wait_time - spawn_time);\n        auto wait = duration_cast<microseconds>(end_time - wait_time);\n        write_to_debug_buffer(format(\"shell execution took {} us (spawn: {}, wait: {})\",\n                                     (size_t)full.count(), (size_t)spawn.count(), (size_t)wait.count()));\n    }\n\n    if (cancelling)\n        throw cancel{};\n\n    return { std::move(stdout_contents), WIFEXITED(status) ? WEXITSTATUS(status) : -1 };\n}\n\nShell ShellManager::spawn(StringView cmdline, const Context& context,\n                          bool open_stdin, const ShellContext& shell_context)\n{\n    auto kak_env = generate_env(cmdline, shell_context.params, context, [&](StringView name, Quoting quoting) {\n        if (auto it = shell_context.env_vars.find(name); it != shell_context.env_vars.end())\n            return it->value;\n        return join(get_val(name, context) | transform(quoter(quoting)), ' ', false);\n    });\n\n    return spawn_shell(m_shell.c_str(), cmdline, shell_context.params, kak_env, open_stdin);\n}\n\nVector<String> ShellManager::get_val(StringView name, const Context& context) const\n{\n    auto env_var = find_if(m_env_vars, [name](const EnvVarDesc& desc) {\n        return desc.prefix ? prefix_match(name, desc.str) : name == desc.str;\n    });\n\n    if (env_var == m_env_vars.end())\n        throw runtime_error(\"no such variable: \" + name);\n\n    return env_var->func(name, context);\n}\n\nCandidateList ShellManager::complete_env_var(StringView prefix,\n                                             ByteCount cursor_pos) const\n{\n    return complete(prefix, cursor_pos,\n                    m_env_vars | transform(&EnvVarDesc::str));\n}\n\n}\n"
  },
  {
    "path": "src/shell_manager.hh",
    "content": "#ifndef shell_manager_hh_INCLUDED\n#define shell_manager_hh_INCLUDED\n\n#include \"array_view.hh\"\n#include \"env_vars.hh\"\n#include \"string.hh\"\n#include \"utils.hh\"\n#include \"unique_descriptor.hh\"\n#include \"completion.hh\"\n\n#include <signal.h>\n#include <sys/wait.h>\n#include <unistd.h>\n\nnamespace Kakoune\n{\n\nclass Context;\n\nstruct ShellContext\n{\n    ConstArrayView<String> params;\n    EnvVarMap env_vars;\n};\n\nstruct EnvVarDesc\n{\n    using Retriever = Vector<String> (*)(StringView name, const Context&);\n\n    StringView str;\n    bool prefix;\n    Retriever func;\n};\n\ninline void closepid(int pid){ kill(pid, SIGTERM); int status = 0; waitpid(pid, &status, 0); }\n\nusing UniqueFd = UniqueDescriptor<::close>;\nusing UniquePid = UniqueDescriptor<closepid>;\n\nstruct Shell\n{\n    UniquePid pid;\n    UniqueFd in;\n    UniqueFd out;\n    UniqueFd err;\n};\n\nclass ShellManager : public Singleton<ShellManager>\n{\npublic:\n    ShellManager(ConstArrayView<EnvVarDesc> builtin_env_vars);\n\n    enum class Flags\n    {\n        None = 0,\n        WaitForStdout = 1\n    };\n    friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; }\n\n    std::pair<String, int> eval(StringView cmdline, const Context& context,\n                                FunctionRef<StringView ()> stdin_generator,\n                                Flags flags = Flags::WaitForStdout,\n                                const ShellContext& shell_context = {});\n\n    std::pair<String, int> eval(StringView cmdline, const Context& context,\n                                StringView in,\n                                Flags flags = Flags::WaitForStdout,\n                                const ShellContext& shell_context = {})\n    {\n        return eval(cmdline, context,\n                    [in]() mutable { return std::exchange(in, StringView{}); },\n                    flags, shell_context);\n    }\n\n    Shell spawn(StringView cmdline,\n                const Context& context,\n                bool open_stdin,\n                const ShellContext& shell_context = {});\n\n    Vector<String> get_val(StringView name, const Context& context) const;\n\n    CandidateList complete_env_var(StringView prefix, ByteCount cursor_pos) const;\n\nprivate:\n    String m_shell;\n\n    ConstArrayView<EnvVarDesc> m_env_vars;\n};\n\n}\n\n#endif // shell_manager_hh_INCLUDED\n"
  },
  {
    "path": "src/string.cc",
    "content": "#include \"string.hh\"\n\n#include <cstring>\n#include \"assert.hh\"\n#include \"unit_tests.hh\"\n\nnamespace Kakoune\n{\nnamespace\n{\n// Avoid including all of <algorithm> just for this.\nconstexpr auto max(auto lhs, auto rhs) { return lhs > rhs ? lhs : rhs;}\nconstexpr auto min(auto lhs, auto rhs) { return lhs < rhs ? lhs : rhs;}\n}\n\nString::Data::Data(const char* data, size_t size, size_t capacity)\n{\n    if (capacity > Short::capacity)\n    {\n        kak_assert(capacity <= Long::max_capacity);\n        u.l.ptr = Alloc{}.allocate(capacity+1);\n        u.l.size = size;\n        u.l.capacity = (capacity & Long::max_capacity);\n        u.l.mode = Long::active_mask;\n\n        if (data != nullptr)\n            memcpy(u.l.ptr, data, size);\n        u.l.ptr[size] = 0;\n    }\n    else\n        set_short(data, size);\n}\n\nString::Data& String::Data::operator=(const Data& other)\n{\n    if (&other == this)\n        return *this;\n\n    const size_t new_size = other.size();\n    reserve<false>(new_size);\n    memcpy(data(), other.data(), new_size+1);\n    set_size(new_size);\n\n    return *this;\n}\n\nString::Data& String::Data::operator=(Data&& other) noexcept\n{\n    if (&other == this)\n        return *this;\n\n    release();\n\n    if (other.is_long())\n    {\n        u.l = other.u.l;\n        other.set_empty();\n    }\n    else\n        u.s = other.u.s;\n\n    return *this;\n}\n\ntemplate<bool copy>\nvoid String::Data::reserve(size_t new_capacity)\n{\n    auto const current_capacity = capacity();\n    if (current_capacity != 0 and new_capacity <= current_capacity)\n        return;\n\n    if (!is_long() and new_capacity <= Short::capacity)\n        return;\n\n    kak_assert(new_capacity <= Long::max_capacity);\n    new_capacity = max(new_capacity, // Do not upgrade new_capacity to be over limit.\n                       min(current_capacity * 2, Long::max_capacity));\n\n    char* new_ptr = Alloc{}.allocate(new_capacity+1);\n    if (copy)\n    {\n        memcpy(new_ptr, data(), size()+1);\n    }\n    release();\n\n    u.l.size = size();\n    u.l.ptr = new_ptr;\n    u.l.capacity = (new_capacity & Long::max_capacity);\n    u.l.mode = Long::active_mask;\n}\n\ntemplate void String::Data::reserve<true>(size_t);\ntemplate void String::Data::reserve<false>(size_t);\n\nvoid String::Data::force_size(size_t new_size)\n{\n    reserve<false>(new_size);\n    set_size(new_size);\n    data()[new_size] = 0;\n}\n\nvoid String::Data::append(const char* str, size_t len)\n{\n    if (len == 0)\n        return;\n\n    const size_t new_size = size() + len;\n    reserve(new_size);\n\n    memcpy(data() + size(), str, len);\n    set_size(new_size);\n    data()[new_size] = 0;\n}\n\nvoid String::Data::clear()\n{\n    release();\n    set_empty();\n}\n\nString::String(Codepoint cp, CharCount count)\n{\n    reserve(utf8::codepoint_size(cp) * (int)count);\n    while (count-- > 0)\n        utf8::dump(std::back_inserter(*this), cp);\n}\n\nString::String(Codepoint cp, ColumnCount count)\n{\n    int cp_count = (int)(count / max(codepoint_width(cp), 1_col));\n    reserve(utf8::codepoint_size(cp) * cp_count);\n    while (cp_count-- > 0)\n        utf8::dump(std::back_inserter(*this), cp);\n}\n\nvoid String::resize(ByteCount size, char c)\n{\n    const size_t target_size = (size_t)size;\n    const size_t current_size = m_data.size();\n    if (target_size < current_size)\n        m_data.set_size(target_size);\n    else if (target_size > current_size)\n    {\n        m_data.reserve(target_size);\n        m_data.set_size(target_size);\n        for (auto i = current_size; i < target_size; ++i)\n            m_data.data()[i] = c;\n    }\n    data()[target_size] = 0;\n}\n\nvoid String::Data::set_size(size_t size)\n{\n    if (is_long())\n        u.l.size = size;\n    else\n        u.s.remaining_size = Short::capacity - size;\n}\n\nvoid String::Data::set_short(const char* data, size_t size)\n{\n    kak_assert(size <= Short::capacity);\n    u.s.remaining_size = Short::capacity - size;\n    if (data != nullptr)\n        memcpy(u.s.string, data, size);\n    if (size != Short::capacity) // in this case, remaining_size is the null terminator\n        u.s.string[size] = 0;\n}\n\nUnitTest test_data{[]{\n  using Data = String::Data;\n    { // Basic data usage.\n        Data data;\n        kak_assert(data.size() == 0);\n        kak_assert(not data.is_long());\n        kak_assert(data.capacity() == 23);\n\n        // Should be SSO-ed.\n        data.append(\"test\", 4);\n        kak_assert(data.size() == 4);\n        kak_assert(data.capacity() == 23);\n        kak_assert(not data.is_long());\n        kak_assert(data.data() == StringView(\"test\"));\n    }\n    {\n        char large_buf[2048];\n        memset(large_buf, 'x', 2048);\n        Data data(large_buf, 2048);\n        kak_assert(data.size() == 2048);\n        kak_assert(data.capacity() >= 2048);\n\n        data.clear();\n        kak_assert(data.size() == 0);\n        kak_assert(not data.is_long());\n        kak_assert(data.capacity() == 23);\n    }\n}};\n\nconst String String::ms_empty;\n\n}\n"
  },
  {
    "path": "src/string.hh",
    "content": "#ifndef string_hh_INCLUDED\n#define string_hh_INCLUDED\n\n#include \"memory.hh\"\n#include \"hash.hh\"\n#include \"units.hh\"\n#include \"utf8.hh\"\n\n#include <climits>\n#include <cstddef>\n#include <cstring>\n\nnamespace Kakoune\n{\n\nclass StringView;\n\ntemplate<typename Type, typename CharType>\nclass StringOps\n{\npublic:\n    using value_type = CharType;\n\n    friend constexpr size_t hash_value(const Type& str)\n    {\n        return fnv1a(str.data(), (int)str.length());\n    }\n\n    using iterator = CharType*;\n    using const_iterator = const CharType*;\n    using reverse_iterator = std::reverse_iterator<iterator>;\n    using const_reverse_iterator = std::reverse_iterator<const_iterator>;\n\n    [[gnu::always_inline]]\n    iterator begin() { return type().data(); }\n\n    [[gnu::always_inline]]\n    const_iterator begin() const { return type().data(); }\n\n    [[gnu::always_inline]]\n    iterator end() { return type().data() + type().length(); }\n\n    [[gnu::always_inline]]\n    const_iterator end() const { return type().data() + type().length(); }\n\n    reverse_iterator rbegin() { return reverse_iterator{end()}; }\n    const_reverse_iterator rbegin() const { return const_reverse_iterator{end()}; }\n\n    reverse_iterator rend() { return reverse_iterator{begin()}; }\n    const_reverse_iterator rend() const { return const_reverse_iterator{begin()}; }\n\n    CharType& front() { return *type().data(); }\n    const CharType& front() const { return *type().data(); }\n    CharType& back() { return type().data()[(int)type().length() - 1]; }\n    const CharType& back() const { return type().data()[(int)type().length() - 1]; }\n\n    [[gnu::always_inline]]\n    CharType& operator[](ByteCount pos) { return type().data()[(int)pos]; }\n\n    [[gnu::always_inline]]\n    const CharType& operator[](ByteCount pos) const { return type().data()[(int)pos]; }\n\n    Codepoint operator[](CharCount pos) const\n    { return utf8::codepoint(utf8::advance(begin(), end(), pos), end()); }\n\n    CharCount char_length() const { return utf8::distance(begin(), end()); }\n    ColumnCount column_length() const { return utf8::column_distance(begin(), end()); }\n\n    [[gnu::always_inline]]\n    bool empty() const { return type().length() == 0_byte; }\n\n    bool starts_with(StringView str) const;\n    bool ends_with(StringView str) const;\n\n    ByteCount byte_count_to(CharCount count) const\n    { return utf8::advance(begin(), end(), count) - begin(); }\n\n    ByteCount byte_count_to(ColumnCount count) const\n    { return utf8::advance(begin(), end(), count) - begin(); }\n\n    CharCount char_count_to(ByteCount count) const\n    { return utf8::distance(begin(), begin() + count); }\n\n    ColumnCount column_count_to(ByteCount count) const\n    { return utf8::column_distance(begin(), begin() + count); }\n\n    StringView substr(ByteCount from, ByteCount length = -1) const;\n    StringView substr(CharCount from, CharCount length = -1) const;\n    StringView substr(ColumnCount from, ColumnCount length = -1) const;\n\nprivate:\n    [[gnu::always_inline]]\n    Type& type() { return *static_cast<Type*>(this); }\n    [[gnu::always_inline]]\n    const Type& type() const { return *static_cast<const Type*>(this); }\n};\n\nconstexpr ByteCount strlen(const char* s)\n{\n    int i = 0;\n    while (*s++ != 0)\n        ++i;\n    return {i};\n}\n\nclass String : public StringOps<String, char>\n{\npublic:\n    String() {}\n    String(const char* content) : m_data(content, (size_t)strlen(content)) {}\n    String(const char* content, ByteCount len) : m_data(content, (size_t)len) {}\n    String(const char* begin, const char* end) : m_data(begin, end-begin) {}\n    explicit String(Codepoint cp, CharCount count = 1);\n    explicit String(Codepoint cp, ColumnCount count);\n    explicit String(StringView str);\n\n    struct NoCopy{};\n    String(NoCopy, StringView str);\n\n    static String no_copy(StringView str);\n\n    [[gnu::always_inline]]\n    char* data() { return m_data.data(); }\n\n    [[gnu::always_inline]]\n    const char* data() const { return m_data.data(); }\n\n    [[gnu::always_inline]]\n    ByteCount length() const { return m_data.size(); }\n\n    [[gnu::always_inline]]\n    const char* c_str() const { return m_data.data(); }\n\n    [[gnu::always_inline]]\n    void append(const char* data, ByteCount count) { m_data.append(data, (size_t)count); }\n\n    void clear() { m_data.clear(); }\n\n    void push_back(char c) { m_data.append(&c, 1); }\n    void force_size(ByteCount size) { m_data.force_size((size_t)size); }\n    void reserve(ByteCount size) { m_data.reserve((size_t)size); }\n    void resize(ByteCount size, char c);\n\n    static const String ms_empty;\n    static constexpr const char* option_type_name = \"str\";\n\n    // String data storage using small string optimization.\n    //\n    // The MSB of the last byte is used to flag if we are using the allocated buffer\n    // (1) or in-situ storage, the small one (0). That means the allocated capacity\n    // cannot use its most significant byte, so we effectively limit capacity to\n    // 2^24 on 32bit arch, and 2^56 on 64bit.\n    //\n    // There is also a special NoCopy mode in which the data referred to is un-owned.\n    // It is indicated by being in Long mode with capacity == 0.\n    struct Data\n    {\n        using Alloc = Allocator<char, MemoryDomain::String>;\n\n        Data() { set_empty(); }\n        Data(NoCopy, const char* data, size_t size) : u{Long{const_cast<char*>(data),\n                                                             size,\n                                                             /*capacity=*/0,\n                                                             /*mode=*/Long::active_mask}} {}\n\n        Data(const char* data, size_t size, size_t capacity);\n        Data(const char* data, size_t size) : Data(data, size, size) {}\n        Data(const Data& other) : Data{other.data(), other.size()} {}\n\n        ~Data() { release(); }\n        Data(Data&& other) noexcept : u{other.u} { other.set_empty(); }\n        Data& operator=(const Data& other);\n        Data& operator=(Data&& other) noexcept;\n\n        bool is_long() const { return (u.l.mode & Long::active_mask) != 0; }\n        size_t size() const { return is_long() ? u.l.size : (Short::capacity - u.s.remaining_size); }\n        size_t capacity() const { return is_long() ? u.l.capacity : Short::capacity; }\n\n        const char* data() const { return is_long() ? u.l.ptr : u.s.string; }\n        char* data() { return is_long() ? u.l.ptr : u.s.string; }\n\n        template<bool copy = true>\n        void reserve(size_t new_capacity);\n        void set_size(size_t size);\n        void force_size(size_t new_size);\n        void append(const char* str, size_t len);\n        void clear();\n\n    private:\n        struct Long\n        {\n            static constexpr size_t capacity_bits = CHAR_BIT * (sizeof(size_t) - 1);\n            static constexpr size_t max_capacity = ((size_t)1 << capacity_bits) - 1;\n\n            char* ptr;\n            size_t size;\n            size_t capacity : capacity_bits;\n            unsigned char mode;\n            static constexpr unsigned char active_mask = 0b1000'0000;\n        };\n        static_assert(sizeof(Long) == sizeof(char*) * 3);\n\n        struct Short\n        {\n            static constexpr size_t capacity = sizeof(Long) - 1;\n            char string[capacity];\n            // When string is full remaining_size will be 0 and be the null terminator.\n            // When string is empty remaining size will be 23 (0b00010111)\n            // and not collide with Long::active_mask.\n            unsigned char remaining_size;\n        };\n        static_assert(offsetof(Long, mode) == offsetof(Short, remaining_size));\n\n        union\n        {\n            Long l;\n            Short s;\n        } u;\n\n        void release()\n        {\n            if (is_long() and (u.l.capacity != 0))\n                Alloc{}.deallocate(u.l.ptr, u.l.capacity+1);\n        }\n\n        void set_empty() { u.s.remaining_size = Short::capacity; u.s.string[0] = '\\0'; }\n        void set_short(const char* data, size_t size);\n    };\n\nprivate:\n    Data m_data;\n};\n\nclass StringView : public StringOps<StringView, const char>\n{\npublic:\n    StringView() = default;\n    constexpr StringView(const char* data, ByteCount length)\n        : m_data{data}, m_length{length} {}\n    constexpr StringView(const char* data) : m_data{data}, m_length{data ? strlen(data) : 0} {}\n    constexpr StringView(const char* begin, const char* end) : m_data{begin}, m_length{(int)(end - begin)} {}\n    StringView(const String& str) : m_data{str.data()}, m_length{(int)str.length()} {}\n    StringView(const char& c) : m_data(&c), m_length(1) {}\n    StringView(int c) = delete;\n    StringView(Codepoint c) = delete;\n\n    [[gnu::always_inline]]\n    constexpr const char* data() const { return m_data; }\n\n    [[gnu::always_inline]]\n    constexpr ByteCount length() const { return m_length; }\n\n    String str() const { return {m_data, m_length}; }\n\n    struct ZeroTerminatedString\n    {\n        ZeroTerminatedString(const char* begin, const char* end)\n        {\n            if (*end == '\\0')\n                unowned = begin;\n            else\n                owned = String::Data(begin, end - begin);\n        }\n        operator const char*() const { return unowned ? unowned : owned.data(); }\n\n    private:\n        String::Data owned;\n        const char* unowned = nullptr;\n    };\n    ZeroTerminatedString zstr() const { return {begin(), end()}; }\n\nprivate:\n    const char* m_data;\n    ByteCount m_length;\n};\n\nstatic_assert(std::is_trivial<StringView>::value, \"\");\n\ntemplate<> struct HashCompatible<String, StringView> : std::true_type {};\ntemplate<> struct HashCompatible<StringView, String> : std::true_type {};\n\ninline String::String(StringView str) : String{str.begin(), str.length()} {}\ninline String::String(NoCopy, StringView str) : m_data{NoCopy{}, str.begin(), (size_t)str.length()} {}\ninline String String::no_copy(StringView str) { return {NoCopy{}, str}; }\n\ntemplate<typename Type, typename CharType>\ninline StringView StringOps<Type, CharType>::substr(ByteCount from, ByteCount length) const\n{\n    const auto str_length = type().length();\n    const auto max_length = str_length - from;\n    kak_assert(from >= 0 and max_length >= 0);\n    return StringView{type().data() + (int)from, length >= 0 and length < max_length ? length : max_length};\n}\n\ntemplate<typename Type, typename CharType>\ninline StringView StringOps<Type, CharType>::substr(CharCount from, CharCount length) const\n{\n    auto beg = utf8::advance(begin(), end(), from);\n    return StringView{beg, length >= 0 ? utf8::advance(beg, end(), length) : end()};\n}\n\ntemplate<typename Type, typename CharType>\ninline StringView StringOps<Type, CharType>::substr(ColumnCount from, ColumnCount length) const\n{\n    auto beg = utf8::advance(begin(), end(), from);\n    return StringView{beg, (length >= 0) ? utf8::advance(beg, end(), length) : end()};\n}\n\ntemplate<typename Type, typename CharType>\ninline bool StringOps<Type, CharType>::starts_with(StringView str) const\n{\n    if (type().length() < str.length())\n        return false;\n    return substr(0, str.length()) == str;\n}\n\ntemplate<typename Type, typename CharType>\ninline bool StringOps<Type, CharType>::ends_with(StringView str) const\n{\n    if (type().length() < str.length())\n        return false;\n    return substr(type().length() - str.length()) == str;\n}\n\ninline String& operator+=(String& lhs, StringView rhs)\n{\n    lhs.append(rhs.data(), rhs.length());\n    return lhs;\n}\n\ninline String operator+(StringView lhs, StringView rhs)\n{\n    String res;\n    res.reserve(lhs.length() + rhs.length());\n    res.append(lhs.data(), lhs.length());\n    res.append(rhs.data(), rhs.length());\n    return res;\n}\n\n[[gnu::always_inline]]\ninline bool operator==(const StringView& lhs, const StringView& rhs)\n{\n    return lhs.length() == rhs.length() and\n           (lhs.empty() or std::memcmp(lhs.begin(), rhs.begin(), (size_t)lhs.length()) == 0);\n}\n\ninline auto operator<=>(const StringView& lhs, const StringView& rhs)\n{\n    auto lit = lhs.begin(), lend = lhs.end(), rit = rhs.begin(), rend = rhs.end();\n    while (lit != lend and rit != rend) {\n         if (auto cmp = *lit++ <=> *rit++; cmp != 0)\n             return cmp;\n    }\n    if (lit == lend and rit == rend)\n        return std::strong_ordering::equal;\n    return lit == lend ? std::strong_ordering::less : std::strong_ordering::greater;\n\n}\n\ninline String operator\"\"_str(const char* str, size_t)\n{\n    return String(str);\n}\n\ninline StringView operator\"\"_sv(const char* str, size_t)\n{\n    return StringView{str};\n}\n\n}\n\n#endif // string_hh_INCLUDED\n"
  },
  {
    "path": "src/string_utils.cc",
    "content": "#include \"string_utils.hh\"\n\n#include \"exception.hh\"\n#include \"utf8_iterator.hh\"\n#include \"unit_tests.hh\"\n#include \"ranges.hh\"\n\nnamespace Kakoune\n{\n\nString trim_indent(StringView str)\n{\n    if (str.empty())\n        return {};\n\n    if (str[0_byte] == '\\n')\n        str = str.substr(1_byte);\n    while (not str.empty() and is_blank(str.back()))\n        str = str.substr(0, str.length() - 1);\n\n    utf8::iterator it{str.begin(), str};\n    while (it != str.end() and is_horizontal_blank(*it))\n        ++it;\n\n    const StringView indent{str.begin(), it.base()};\n    return accumulate(str | split_after<StringView>('\\n') | transform([&](auto&& line) {\n            if (line == \"\\n\")\n                return line;\n            else if (not prefix_match(line, indent))\n                throw runtime_error(\"inconsistent indentation in the string\");\n\n            return line.substr(indent.length());\n        }), String{}, [](String s, StringView l) { return s += l; });\n}\n\nString escape(StringView str, StringView characters, char escape)\n{\n    String res;\n    res.reserve(str.length());\n    auto cbeg = characters.begin(), cend = characters.end();\n    for (auto it = str.begin(), end = str.end(); it != end; )\n    {\n        auto next = std::find_first_of(it, end, cbeg, cend);\n        if (next != end)\n        {\n            res += StringView{it, next+1};\n            res.back() = escape;\n            res += *next;\n            it = next+1;\n        }\n        else\n        {\n            res += StringView{it, next};\n            break;\n        }\n    }\n    return res;\n}\n\nString unescape(StringView str, StringView characters, char escape)\n{\n    String res;\n    res.reserve(str.length());\n    for (auto it = str.begin(), end = str.end(); it != end; )\n    {\n        auto next = std::find(it, end, escape);\n        if (next != end and next+1 != end and contains(characters, *(next+1)))\n        {\n            res += StringView{it, next+1};\n            res.back() = *(next+1);\n            it = next + 2;\n        }\n        else\n        {\n            res += StringView{it, next == end ? next : next + 1};\n            it = next == end ? next : next + 1;\n        }\n    }\n    return res;\n}\n\nString indent(StringView str, StringView indent)\n{\n    String res;\n    res.reserve(str.length());\n    bool was_eol = true;\n    for (ByteCount i = 0; i < str.length(); ++i)\n    {\n        if (was_eol)\n            res += indent;\n        res += str[i];\n        was_eol = is_eol(str[i]);\n    }\n    return res;\n}\n\nString replace(StringView str, StringView substr, StringView replacement)\n{\n    String res;\n    for (auto it = str.begin(); it != str.end(); )\n    {\n        auto match = std::search(it, str.end(), substr.begin(), substr.end());\n        res += StringView{it, match};\n        if (match == str.end())\n            break;\n\n        res += replacement;\n        it = match + substr.length();\n    }\n    return res;\n}\n\nString left_pad(StringView str, ColumnCount size, Codepoint c)\n{\n    return String(c, std::max(0_col, size - str.column_length())) + str.substr(0, size);\n}\n\nString right_pad(StringView str, ColumnCount size, Codepoint c)\n{\n    return str.substr(0, size) + String(c, std::max(0_col, size - str.column_length()));\n}\n\nOptional<int> str_to_int_ifp(StringView str)\n{\n    bool negative = not str.empty() and str[0] == '-';\n    if (negative)\n        str = str.substr(1_byte);\n    if (str.empty())\n        return {};\n\n    unsigned int res = 0;\n    for (auto c : str)\n    {\n        if (c < '0' or c > '9')\n            return {};\n        res = res * 10 + c - '0';\n    }\n    return negative ? -res : res;\n}\n\nint str_to_int(StringView str)\n{\n    if (auto val = str_to_int_ifp(str))\n        return *val;\n    throw runtime_error{str + \" is not a number\"};\n}\n\nbool subsequence_match(StringView str, StringView subseq)\n{\n    auto it = str.begin();\n    for (auto& c : subseq)\n    {\n        if (it == str.end())\n            return false;\n        while (*it != c)\n        {\n            if (++it == str.end())\n                return false;\n        }\n        ++it;\n    }\n    return true;\n}\n\nString expand_tabs(StringView line, ColumnCount tabstop, ColumnCount col)\n{\n    String res;\n    res.reserve(line.length());\n    for (auto it = line.begin(), end = line.end(); it != end; )\n    {\n        if (*it == '\\t')\n        {\n            ColumnCount end_col = (col / tabstop + 1) * tabstop;\n            res += String{' ', end_col - col};\n            col = end_col;\n            ++it;\n        }\n        else\n        {\n            auto char_beg = it;\n            auto cp = utf8::read_codepoint(it, end);\n            res += {char_beg, it};\n            col += codepoint_width(cp);\n        }\n    }\n    return res;\n}\n\nString double_up(StringView s, StringView characters)\n{\n    String res;\n    auto pos = s.begin();\n    for (auto it = s.begin(), end = s.end(); it != end; ++it)\n    {\n        if (contains(characters, *it))\n        {\n            res += StringView{pos, it+1};\n            res += *it;\n            pos = it+1;\n        }\n    }\n    res += StringView{pos, s.end()};\n    return res;\n}\n\nUnitTest test_string{[]()\n{\n    kak_assert(String(\"youpi \") + \"matin\" == \"youpi matin\");\n\n    kak_assert(StringView{\"youpi\"}.starts_with(\"\"));\n    kak_assert(StringView{\"youpi\"}.starts_with(\"you\"));\n    kak_assert(StringView{\"youpi\"}.starts_with(\"youpi\"));\n    kak_assert(not StringView{\"youpi\"}.starts_with(\"youpi!\"));\n\n    kak_assert(StringView{\"youpi\"}.ends_with(\"\"));\n    kak_assert(StringView{\"youpi\"}.ends_with(\"pi\"));\n    kak_assert(StringView{\"youpi\"}.ends_with(\"youpi\"));\n    kak_assert(not StringView{\"youpi\"}.ends_with(\"oup\"));\n\n    kak_assert(trim_indent(\" \") == \"\");\n    kak_assert(trim_indent(\"no-indent\") == \"no-indent\");\n    kak_assert(trim_indent(\"\\nno-indent\") == \"no-indent\");\n    kak_assert(trim_indent(\"\\n  indent\\n  indent\") == \"indent\\nindent\");\n    kak_assert(trim_indent(\"\\n  indent\\n    indent\") == \"indent\\n  indent\");\n    kak_assert(trim_indent(\"\\n  indent\\n  indent\\n   \") == \"indent\\nindent\");\n\n    kak_expect_throw(runtime_error, trim_indent(\"\\n  indent\\nno-indent\"));\n\n    kak_assert(escape(R\"(\\youpi:matin:tchou\\:)\", \":\\\\\", '\\\\') == R\"(\\\\youpi\\:matin\\:tchou\\\\\\:)\");\n    kak_assert(unescape(R\"(\\\\youpi\\:matin\\:tchou\\\\\\:)\", \":\\\\\", '\\\\') == R\"(\\youpi:matin:tchou\\:)\");\n\n    kak_assert(prefix_match(\"tchou kanaky\", \"tchou\"));\n    kak_assert(prefix_match(\"tchou kanaky\", \"tchou kanaky\"));\n    kak_assert(prefix_match(\"tchou kanaky\", \"t\"));\n    kak_assert(not prefix_match(\"tchou kanaky\", \"c\"));\n\n    kak_assert(subsequence_match(\"tchou kanaky\", \"tknky\"));\n    kak_assert(subsequence_match(\"tchou kanaky\", \"knk\"));\n    kak_assert(subsequence_match(\"tchou kanaky\", \"tchou kanaky\"));\n    kak_assert(not subsequence_match(\"tchou kanaky\", \"tchou  kanaky\"));\n\n    kak_assert(format(\"Youhou {1} {} '{0:4}' {2:04} \\\\{}\", 10, \"hehe\", 5) == \"Youhou hehe 5 '  10' 0005 {}\");\n\n    char buffer[20];\n    kak_assert(format_to(buffer, \"Hey {}\", 15) == \"Hey 15\");\n\n    kak_assert(str_to_int(\"5\") == 5);\n    kak_assert(str_to_int(to_string(INT_MAX)) == INT_MAX);\n    kak_assert(str_to_int(to_string(INT_MIN)) == INT_MIN);\n    kak_assert(str_to_int(\"00\") == 0);\n    kak_assert(str_to_int(\"-0\") == 0);\n\n    kak_assert(double_up(R\"('foo%\"bar\"')\", \"'\\\"%\") == R\"(''foo%%\"\"bar\"\"'')\");\n\n    kak_assert(replace(\"tchou/tcha/tchi\", \"/\", \"!!\") == \"tchou!!tcha!!tchi\");\n}};\n\n}\n"
  },
  {
    "path": "src/string_utils.hh",
    "content": "#ifndef string_utils_hh_INCLUDED\n#define string_utils_hh_INCLUDED\n\n#include \"string.hh\"\n#include \"enum.hh\"\n#include \"vector.hh\"\n#include \"optional.hh\"\n#include \"format.hh\"\n#include \"array.hh\"\n\nnamespace Kakoune\n{\n\nString trim_indent(StringView str);\n\nString escape(StringView str, StringView characters, char escape);\nString unescape(StringView str, StringView characters, char escape);\n\ntemplate<char character, char escape>\nString unescape(StringView str)\n{\n    const char to_escape[2] = { character, escape };\n    return unescape(str, {to_escape, 2}, escape);\n}\n\nString indent(StringView str, StringView indent = \"    \");\n\nString replace(StringView str, StringView substr, StringView replacement);\n\nString left_pad(StringView str, ColumnCount size, Codepoint c = ' ');\nString right_pad(StringView str, ColumnCount size, Codepoint c = ' ');\n\ntemplate<typename Container>\nString join(const Container& container, char joiner, bool esc_joiner = true)\n{\n    const char to_escape[2] = { joiner, '\\\\' };\n    String res;\n    for (const auto& str : container)\n    {\n        if (not res.empty())\n            res += joiner;\n        res += esc_joiner ? escape(str, {to_escape, 2}, '\\\\') : str;\n    }\n    return res;\n}\n\ntemplate<typename Container>\nString join(const Container& container, StringView joiner)\n{\n    String res;\n    for (const auto& str : container)\n    {\n        if (not res.empty())\n            res += joiner;\n        res += str;\n    }\n    return res;\n}\n\ninline bool prefix_match(StringView str, StringView prefix)\n{\n    return str.substr(0_byte, prefix.length()) == prefix;\n}\n\nbool subsequence_match(StringView str, StringView subseq);\n\nString expand_tabs(StringView line, ColumnCount tabstop, ColumnCount col = 0);\n\nint str_to_int(StringView str); // throws on error\nOptional<int> str_to_int_ifp(StringView str);\n\nString double_up(StringView s, StringView characters);\n\ninline String quote(StringView s)\n{\n    return format(\"'{}'\", double_up(s, \"'\"));\n}\n\ninline String shell_quote(StringView s)\n{\n    return format(\"'{}'\", replace(s, \"'\", R\"('\\'')\"));\n}\n\nenum class Quoting\n{\n    Raw,\n    Kakoune,\n    Shell\n};\n\nconstexpr auto enum_desc(Meta::Type<Quoting>)\n{\n    return make_array<EnumDesc<Quoting>>({\n        { Quoting::Raw, \"raw\" },\n        { Quoting::Kakoune, \"kakoune\" },\n        { Quoting::Shell, \"shell\" }\n    });\n}\n\ninline auto quoter(Quoting quoting)\n{\n    switch (quoting)\n    {\n        case Quoting::Kakoune: return &quote;\n        case Quoting::Shell: return &shell_quote;\n        case Quoting::Raw:\n        default:\n            return +[](StringView s) { return s.str(); };\n    }\n}\n\ninline String option_to_string(StringView opt, Quoting quoting) { return quoter(quoting)(opt); }\ninline Vector<String> option_to_strings(StringView opt) { return {opt.str()}; }\ninline String option_from_string(Meta::Type<String>, StringView str) { return str.str(); }\ninline bool option_add(String& opt, StringView val) { opt += val; return not val.empty(); }\n\n}\n\n#endif // string_utils_hh_INCLUDED\n"
  },
  {
    "path": "src/terminal_ui.cc",
    "content": "#include \"terminal_ui.hh\"\n\n#include \"display_buffer.hh\"\n#include \"event_manager.hh\"\n#include \"exception.hh\"\n#include \"file.hh\"\n#include \"keys.hh\"\n#include \"ranges.hh\"\n#include \"format.hh\"\n#include \"diff.hh\"\n#include \"string_utils.hh\"\n\n#include <algorithm>\n\n#include <fcntl.h>\n#include <csignal>\n#include <sys/ioctl.h>\n#include <unistd.h>\n#include <strings.h>\n\nnamespace Kakoune\n{\n\nusing std::min;\nusing std::max;\n\nstatic String fix_atom_text(StringView str)\n{\n    String res;\n    auto pos = str.begin();\n    for (auto it = str.begin(), end = str.end(); it != end; ++it)\n    {\n        unsigned char c = *it;\n        if (c <= 0x1F)\n        {\n            res += StringView{pos, it};\n            res += String{Codepoint{(uint32_t)(0x2400 + c)}};\n            pos = it+1;\n        }\n    }\n    res += StringView{pos, str.end()};\n    return res;\n}\n\nstruct TerminalUI::Window::Line\n{\n    struct Atom\n    {\n        String text;\n        ColumnCount skip = 0;\n        Face face;\n\n        ColumnCount length() const { return text.column_length() + skip; }\n        void resize(ColumnCount size)\n        {\n            auto it = text.begin(), end = text.end();\n            while (it != end and size > 0)\n                size -= codepoint_width(utf8::read_codepoint(it, end));\n\n            if (size < 0) // possible if resizing to the middle of a double-width codepoint\n            {\n                kak_assert(size == -1);\n                utf8::to_previous(it, text.begin());\n                skip = 1;\n            }\n            else\n                skip = size;\n            text.resize(it - text.begin(), 0);\n        }\n\n        friend bool operator==(const Atom& lhs, const Atom& rhs) = default;\n        friend size_t hash_value(const Atom& atom) { return hash_values(atom.text, atom.skip, atom.face); }\n    };\n\n    void append(StringView text, ColumnCount skip, Face face)\n    {\n        if (not atoms.empty() and atoms.back().face == face and (atoms.back().skip == 0 or text.empty()))\n        {\n            atoms.back().text += fix_atom_text(text);\n            atoms.back().skip += skip;\n        }\n        else\n            atoms.push_back({fix_atom_text(text), skip, face});\n    }\n\n    void resize(ColumnCount width)\n    {\n        auto it = atoms.begin();\n        ColumnCount column = 0;\n        for (; it != atoms.end() and column < width; ++it)\n            column += it->length();\n\n        if (column < width)\n            append({}, width - column, atoms.empty() ? Face{} : atoms.back().face);\n        else\n        {\n            atoms.erase(it, atoms.end());\n            if (column > width)\n                atoms.back().resize(atoms.back().length() - (column - width));\n        }\n    }\n\n    Vector<Atom>::iterator erase_range(ColumnCount pos, ColumnCount len)\n    {\n        struct Pos{ Vector<Atom>::iterator it; ColumnCount column; };\n        auto find_col = [pos=0_col, it=atoms.begin(), end=atoms.end()](ColumnCount col) mutable {\n            for (; it != end; ++it)\n            {\n                auto atom_len = it->length();\n                if (pos + atom_len >= col)\n                    return Pos{it, col - pos};\n                pos += atom_len;\n            }\n            return Pos{it, 0_col};\n        };\n        Pos begin = find_col(pos);\n        Pos end = find_col(pos+len);\n\n        auto make_tail = [](const Atom& atom, ColumnCount from) {\n            auto it = atom.text.begin(), end = atom.text.end();\n            while (it != end and from > 0)\n                from -= codepoint_width(utf8::read_codepoint(it, end));\n\n            if (from < 0) // can happen if tail starts in the middle of a double-width codepoint\n            {\n                kak_assert(from == -1);\n                return Atom{\" \" + StringView{it, end}, atom.skip, atom.face};\n            }\n            return Atom{{it, end}, atom.skip - from, atom.face};\n        };\n\n        if (begin.it == end.it)\n        {\n            Atom tail = make_tail(*begin.it, end.column);\n            begin.it->resize(begin.column);\n            return (tail.text.empty() and tail.skip == 0) ? begin.it+1 : atoms.insert(begin.it+1, tail);\n        }\n\n        begin.it->resize(begin.column);\n        if (end.column > 0)\n        {\n            if (end.column == end.it->length())\n                ++end.it;\n            else\n                *end.it = make_tail(*end.it, end.column);\n        }\n        return atoms.erase(begin.it+1, end.it);\n    }\n\n    Vector<Atom> atoms;\n};\n\nvoid TerminalUI::Window::create(const DisplayCoord& p, const DisplayCoord& s)\n{\n    kak_assert(p.line >= 0 and p.column >= 0);\n    kak_assert(s.line >= 0 and s.column >= 0);\n    pos = p;\n    size = s;\n    lines.reset(new Line[(int)size.line]);\n}\n\nvoid TerminalUI::Window::destroy()\n{\n    pos = DisplayCoord{};\n    size = DisplayCoord{};\n    lines.reset();\n}\n\nvoid TerminalUI::Window::blit(Window& target)\n{\n    kak_assert(pos.line < target.size.line);\n    LineCount line_index = pos.line;\n    for (auto& line : ArrayView{lines.get(), (size_t)size.line})\n    {\n        line.resize(size.column);\n        auto& target_line = target.lines[(size_t)line_index];\n        target_line.resize(target.size.column);\n        target_line.atoms.insert(target_line.erase_range(pos.column, size.column), line.atoms.begin(), line.atoms.end());\n        if (++line_index == target.size.line)\n            break;\n    }\n}\n\nvoid TerminalUI::Window::draw(DisplayCoord pos,\n                              ConstArrayView<DisplayAtom> atoms,\n                              const Face& default_face)\n{\n    if (pos.line >= size.line) // We might receive an out of date draw command after a resize\n        return;\n\n    lines[(size_t)pos.line].resize(pos.column);\n    for (const DisplayAtom& atom : atoms)\n    {\n        StringView content = atom.content();\n        if (content.empty())\n            continue;\n\n        auto face = merge_faces(default_face, atom.face);\n        if (content.back() == '\\n')\n            lines[(int)pos.line].append(content.substr(0, content.length()-1), 1, face);\n        else\n            lines[(int)pos.line].append(content, 0, face);\n        pos.column += content.column_length();\n    }\n\n    if (pos.column < size.column)\n        lines[(int)pos.line].append({}, size.column - pos.column, default_face);\n}\n\nstruct Writer : BufferedWriter<true>\n{\n    using Writer::BufferedWriter::BufferedWriter;\n    ~Writer() noexcept(false) = default;\n};\n\ntemplate<typename... Args>\nstatic void format_with(Writer& writer, StringView format, Args&&... args)\n{\n    format_with([&](StringView s) { writer.write(s); }, format, std::forward<Args>(args)...);\n}\n\nvoid TerminalUI::Screen::set_face(const Face& face, Writer& writer)\n{\n    static constexpr int fg_table[]{ 39, 30, 31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97 };\n    static constexpr int bg_table[]{ 49, 40, 41, 42, 43, 44, 45, 46, 47, 100, 101, 102, 103, 104, 105, 106, 107 };\n    static constexpr int ul_table[]{ 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };\n    static constexpr const char* attr_table[]{ \"0\", \"4\", \"4:3\", \"21\", \"7\", \"5\", \"1\", \"2\", \"3\", \"9\" };\n\n    auto set_color = [&](bool fg, const Color& color, bool join) {\n        if (join)\n            writer.write(\";\");\n        if (color.isRGB())\n            format_with(writer, \"{};2;{};{};{}\", fg ? 38 : 48, color.r, color.g, color.b);\n        else\n            format_with(writer, \"{}\", (fg ? fg_table : bg_table)[(int)(char)color.color]);\n    };\n\n    if (m_active_face == face)\n        return;\n\n    writer.write(\"\\033[\");\n    bool join = false;\n    if (face.attributes != m_active_face.attributes)\n    {\n        for (int i = 0; i < std::size(attr_table); ++i)\n        {\n            if (face.attributes & (Attribute)(1 << i))\n                format_with(writer, \";{}\", attr_table[i]);\n        }\n        m_active_face.fg = m_active_face.bg = m_active_face.underline = Color::Default;\n        join = true;\n    }\n    if (m_active_face.fg != face.fg)\n    {\n        set_color(true, face.fg, join);\n        join = true;\n    }\n    if (m_active_face.bg != face.bg)\n    {\n        set_color(false, face.bg, join);\n        join = true;\n    }\n    if (m_active_face.underline != face.underline)\n    {\n        if (join)\n            writer.write(\";\");\n        if (face.underline != Color::Default)\n        {\n            if (face.underline.isRGB())\n                format_with(writer, \"58:2::{}:{}:{}\", face.underline.r, face.underline.g, face.underline.b);\n            else\n                format_with(writer, \"58:5:{}\", ul_table[(int)(char)face.underline.color]);\n        }\n        else\n            format_with(writer, \"59\");\n    }\n    writer.write(\"m\");\n\n    m_active_face = face;\n}\n\nvoid TerminalUI::Screen::output(bool force, bool synchronized, Writer& writer)\n{\n    if (not lines)\n        return;\n\n    if (force)\n    {\n        std::fill_n(hashes.get(), (size_t)size.line, 0);\n        writer.write(\"\\033[m\");\n        m_active_face = Face{};\n    }\n\n    auto hash_line = [](const Line& line) {\n        return (hash_value(line.atoms) << 1) | 1; // ensure non-zero\n    };\n\n    auto output_line = [&](const Line& line) {\n        ColumnCount pending_move = 0;\n        for (auto& [text, skip, face] : line.atoms)\n        {\n            if (text.empty() and skip == 0)\n                continue;\n\n            if (pending_move != 0)\n            {\n                format_with(writer, \"\\033[{}C\", (int)pending_move);\n                pending_move = 0;\n            }\n            set_face(face, writer);\n            writer.write(text);\n            if (skip > 3 and face.attributes == Attribute{})\n            {\n                writer.write(\"\\033[K\");\n                pending_move = skip;\n            }\n            else if (skip > 0)\n                writer.write(String{' ', skip});\n        }\n    };\n\n    if (synchronized)\n    {\n        writer.write(\"\\033[?2026h\"); // begin synchronized update\n\n        struct Change { int keep; int add; int del; };\n        Vector<Change> changes{Change{}};\n        auto new_hashes = ArrayView{lines.get(), (size_t)size.line} | transform(hash_line);\n        for_each_diff(hashes.get(), (int)size.line,\n                      new_hashes.begin(), (int)size.line,\n                      [&changes](DiffOp op, int len) mutable {\n            switch (op)\n            {\n                case DiffOp::Keep:\n                    changes.push_back({len, 0, 0});\n                    break;\n                case DiffOp::Add:\n                    changes.back().add += len;\n                    break;\n                case DiffOp::Remove:\n                    changes.back().del += len;\n                    break;\n            }\n        });\n        std::copy(new_hashes.begin(), new_hashes.end(), hashes.get());\n\n        int line = 0;\n        for (auto& change : changes)\n        {\n            line += change.keep;\n            if (int del = change.del - change.add; del > 0)\n            {\n                format_with(writer, \"\\033[{}H\\033[{}M\", line + 1, del);\n                line -= del;\n            }\n            line += change.del;\n        }\n\n        line = 0;\n        for (auto& change : changes)\n        {\n            line += change.keep;\n            for (int i = 0; i < change.add; ++i)\n            {\n                if (int add = change.add - change.del; i == 0 and add > 0)\n                    format_with(writer, \"\\033[{}H\\033[{}L\", line + 1, add);\n                else\n                    format_with(writer, \"\\033[{}H\", line + 1);\n\n                output_line(lines[line++]);\n            }\n        }\n\n        writer.write(\"\\033[?2026l\"); // end synchronized update\n    }\n    else\n    {\n        for (int line = 0; line < (int)size.line; ++line)\n        {\n            auto hash = hash_line(lines[line]);\n            if (hash == hashes[line])\n                continue;\n            hashes[line] = hash;\n\n            format_with(writer, \"\\033[{}H\", line + 1);\n            output_line(lines[line]);\n        }\n    }\n}\n\nconstexpr int TerminalUI::default_shift_function_key;\n\nstatic constexpr StringView assistant_cat[] =\n    { R\"(  ___            )\",\n      R\"( (__ \\           )\",\n      R\"(   / /          ╭)\",\n      R\"(  .' '·.        │)\",\n      R\"( '      ”       │)\",\n      R\"( ╰       /\\_/|  │)\",\n      R\"(  | .         \\ │)\",\n      R\"(  ╰_J`    | | | ╯)\",\n      R\"(      ' \\__- _/  )\",\n      R\"(      \\_\\   \\_\\  )\",\n      R\"(                 )\"};\n\nstatic constexpr StringView assistant_clippy[] =\n    { \" ╭──╮   \",\n      \" │  │   \",\n      \" @  @  ╭\",\n      \" ││ ││ │\",\n      \" ││ ││ ╯\",\n      \" │╰─╯│  \",\n      \" ╰───╯  \",\n      \"        \" };\n\nstatic constexpr StringView assistant_dilbert[] =\n    { R\"(  დოოოოოდ   )\",\n      R\"(  |     |   )\",\n      R\"(  |     |  ╭)\",\n      R\"(  |-ᱛ ᱛ-|  │)\",\n      R\"( Ͼ   ∪   Ͽ │)\",\n      R\"(  |     |  ╯)\",\n      R\"( ˏ`-.ŏ.-´ˎ  )\",\n      R\"(     @      )\",\n      R\"(      @     )\",\n      R\"(            )\"};\n\ntemplate<typename T> T sq(T x) { return x * x; }\n\nstatic sig_atomic_t resize_pending = 0;\nstatic sig_atomic_t terminal_hungup = 0;\n\ntemplate<sig_atomic_t* signal_flag>\nstatic void signal_handler(int)\n{\n    *signal_flag = 1;\n    EventManager::instance().force_signal(0);\n}\n\nTerminalUI::TerminalUI()\n    : m_cursor_pos{},\n      m_stdin_watcher{STDIN_FILENO, FdEvents::Read, EventMode::Urgent,\n                      [this](FDWatcher&, FdEvents, EventMode) {\n        if (not m_on_key)\n            return;\n\n        while (auto key = get_next_key())\n        {\n            if (*key == ctrl('z'))\n                kill(0, SIGTSTP); // We suspend at this line\n            else if (*key != Key::Invalid)\n                m_on_key(*key);\n        }\n      }},\n      m_assistant(assistant_clippy)\n{\n    if (not isatty(1))\n        throw runtime_error(\"stdout is not a tty\");\n\n    tcgetattr(STDIN_FILENO, &m_original_termios);\n\n    setup_terminal();\n    set_raw_mode();\n    enable_mouse(true);\n\n    set_signal_handler(SIGWINCH, &signal_handler<&resize_pending>);\n    set_signal_handler(SIGHUP, &signal_handler<&terminal_hungup>);\n    set_signal_handler(SIGTSTP, [](int){ TerminalUI::instance().suspend(); });\n\n    check_resize(true);\n    redraw(false);\n}\n\nTerminalUI::~TerminalUI()\n{\n    if (not terminal_hungup) {\n        enable_mouse(false);\n        restore_terminal();\n        tcsetattr(STDIN_FILENO, TCSAFLUSH, &m_original_termios);\n    }\n    set_signal_handler(SIGWINCH, SIG_DFL);\n    set_signal_handler(SIGHUP, SIG_IGN);\n    set_signal_handler(SIGTSTP, SIG_DFL);\n}\n\nvoid TerminalUI::suspend()\n{\n    if (m_on_key)\n        m_on_key(Key::FocusOut);\n\n    bool mouse_enabled = m_mouse_enabled;\n    enable_mouse(false);\n    tcsetattr(STDIN_FILENO, TCSAFLUSH, &m_original_termios);\n    restore_terminal();\n\n    auto current = set_signal_handler(SIGTSTP, SIG_DFL);\n    sigset_t unblock_sigtstp, old_mask;\n    sigemptyset(&unblock_sigtstp);\n    sigaddset(&unblock_sigtstp, SIGTSTP);\n    sigprocmask(SIG_UNBLOCK, &unblock_sigtstp, &old_mask);\n\n    raise(SIGTSTP); // suspend here\n\n    set_signal_handler(SIGTSTP, current);\n    sigprocmask(SIG_SETMASK, &old_mask, nullptr);\n\n    setup_terminal();\n    check_resize(true);\n    set_raw_mode();\n    enable_mouse(mouse_enabled);\n\n    refresh(true);\n\n    if (m_on_key)\n        m_on_key(Key::FocusIn);\n}\n\nvoid TerminalUI::set_raw_mode() const\n{\n    termios attr = m_original_termios;\n    attr.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);\n    attr.c_oflag &= ~OPOST;\n    attr.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);\n    attr.c_lflag |= NOFLSH;\n    attr.c_cflag &= ~(CSIZE | PARENB);\n    attr.c_cflag |= CS8;\n    attr.c_cc[VMIN] = attr.c_cc[VTIME] = 0;\n\n    tcsetattr(STDIN_FILENO, TCSANOW, &attr);\n}\n\nvoid TerminalUI::redraw(bool force)\n{\n    m_window.blit(m_screen);\n\n    if (m_menu.columns != 0 or m_menu.pos.column > m_status_len)\n        m_menu.blit(m_screen);\n\n    m_info.blit(m_screen);\n\n    Writer writer{STDOUT_FILENO};\n    m_screen.output(force, (bool)m_synchronized, writer);\n\n    auto set_cursor_pos = [&](DisplayCoord c) {\n        format_with(writer, \"\\033[{};{}H\", (int)c.line + 1, (int)c.column + 1);\n    };\n    if (m_status_cursor_pos > 0)\n        set_cursor_pos({m_status_on_top ? 0 : m_dimensions.line, m_status_cursor_pos});\n    else\n        set_cursor_pos(m_cursor_pos + content_line_offset());\n}\n\nvoid TerminalUI::refresh(bool force)\n{\n    if (m_dirty or force)\n        redraw(force);\n    m_dirty = false;\n}\n\nstatic const DisplayLine empty_line = { String(\" \"), {} };\n\nvoid TerminalUI::draw(const DisplayBuffer& display_buffer,\n                      DisplayCoord cursor_pos,\n                      const Face& default_face,\n                      const Face& padding_face,\n                      ColumnCount)\n{\n    check_resize();\n\n    const DisplayCoord dim = dimensions();\n    const LineCount line_offset = content_line_offset();\n    LineCount line_index = line_offset;\n    for (const DisplayLine& line : display_buffer.lines())\n        m_window.draw(line_index++, line.atoms(), default_face);\n\n    auto face = merge_faces(default_face, padding_face);\n\n    DisplayAtom padding{String{m_padding_char, m_padding_fill ? dim.column : 1}};\n\n    while (line_index < dim.line + line_offset)\n        m_window.draw(line_index++, padding, face);\n\n    m_cursor_pos = cursor_pos;\n\n    m_dirty = true;\n}\n\nvoid TerminalUI::draw_status(const DisplayLine& prompt,\n                             const DisplayLine& content,\n                             const ColumnCount cursor_pos,\n                             const DisplayLine& mode_line,\n                             const Face& default_face)\n{\n    const LineCount status_line_pos = m_status_on_top ? 0 : m_dimensions.line;\n\n    auto status_width = m_dimensions.column - prompt.length();\n    if (cursor_pos < m_status_pos)\n        m_status_pos = cursor_pos;\n    if (cursor_pos >= m_status_pos + status_width)\n        m_status_pos = cursor_pos + 1 - status_width;\n\n    m_status_cursor_pos = cursor_pos >= 0 ? prompt.length() + cursor_pos : -1;\n\n    auto trimmed_content = content;\n    trimmed_content.trim(m_status_pos, status_width);\n    m_window.draw(status_line_pos, prompt.atoms(), default_face);\n    m_window.draw(DisplayCoord{status_line_pos, prompt.length()}, trimmed_content.atoms(), default_face);\n\n    const auto mode_len = mode_line.length();\n    m_status_len = prompt.length() + trimmed_content.length();\n    const auto remaining = m_dimensions.column - m_status_len;\n    if (mode_len < remaining)\n    {\n        ColumnCount col = m_dimensions.column - mode_len;\n        m_window.draw({status_line_pos, col}, mode_line.atoms(), default_face);\n    }\n    else if (remaining > 2)\n    {\n        DisplayLine trimmed_mode_line = mode_line;\n        trimmed_mode_line.trim(mode_len + 2 - remaining, remaining - 2);\n        trimmed_mode_line.insert(trimmed_mode_line.begin(), { \"…\", {} });\n        kak_assert(trimmed_mode_line.length() == remaining - 1);\n\n        ColumnCount col = m_dimensions.column - remaining + 1;\n        m_window.draw({status_line_pos, col}, trimmed_mode_line.atoms(), default_face);\n    }\n\n    if (m_set_title)\n    {\n        Writer writer{STDOUT_FILENO};\n        writer.write(\"\\033]2;\");\n        auto write_escaped = [&](StringView str) {\n            for (auto it = str.begin(), end = str.end(); it != end; utf8::to_next(it, end))\n                writer.write((*it >= 0x20 and *it <= 0x7e) ? *it : '?');\n        };\n        if (not m_title)\n        {\n            for (auto& atom : mode_line)\n                write_escaped(atom.content());\n            writer.write(\" - Kakoune\");\n        }\n        else\n            write_escaped(*m_title);\n        writer.write(\"\\007\");\n    }\n\n    m_dirty = true;\n}\n\nvoid TerminalUI::check_resize(bool force)\n{\n    if (not force and not resize_pending)\n        return;\n\n    resize_pending = 0;\n\n    const int fd = open(\"/dev/tty\", O_RDWR);\n    if (fd < 0)\n        return;\n    auto close_fd = OnScopeEnd([fd]{ ::close(fd); });\n\n    DisplayCoord terminal_size{24_line, 80_col};\n    if (winsize ws; ioctl(fd, TIOCGWINSZ, &ws) == 0 and ws.ws_row > 0 and ws.ws_col > 0)\n        terminal_size = {ws.ws_row, ws.ws_col};\n\n    const bool info = (bool)m_info;\n    const bool menu = (bool)m_menu;\n    if (m_window) m_window.destroy();\n    if (info) m_info.destroy();\n    if (menu) m_menu.destroy();\n\n    m_window.create({0, 0}, terminal_size);\n    m_screen.create({0, 0}, terminal_size);\n    m_screen.hashes.reset(new size_t[(int)terminal_size.line]{});\n    kak_assert(m_window);\n\n    m_dimensions = terminal_size - 1_line;\n\n    // if (char* csr = tigetstr((char*)\"csr\"))\n    //     putp(tparm(csr, 0, ws.ws_row));\n\n    if (menu)\n        menu_show(Vector<DisplayLine>(std::move(m_menu.items)),\n                  m_menu.anchor, m_menu.fg, m_menu.bg, m_menu.style);\n    if (info)\n        info_show(m_info.title, m_info.content, m_info.anchor, m_info.face, m_info.style);\n\n    set_resize_pending();\n}\n\nOptional<Key> TerminalUI::get_next_key()\n{\n    if (terminal_hungup)\n    {\n        set_signal_handler(SIGWINCH, SIG_DFL);\n        set_signal_handler(SIGHUP, SIG_IGN);\n        if (m_window)\n            m_window.destroy();\n        m_stdin_watcher.disable();\n        return {};\n    }\n\n    check_resize();\n\n    if (m_resize_pending)\n    {\n        m_resize_pending = false;\n        return resize(dimensions());\n    }\n\n    static auto get_char = []() -> Optional<unsigned char> {\n        if (not fd_readable(STDIN_FILENO))\n            return {};\n\n        if (unsigned char c = 0; read(STDIN_FILENO, &c, 1) == 1)\n            return c;\n\n        terminal_hungup = 1;\n        return {};\n    };\n\n    const auto c = get_char();\n    if (not c)\n        return {};\n\n    static constexpr auto control = [](char c) { return c & 037; };\n\n    auto convert = [this](Codepoint c) -> Codepoint {\n        if (c == control('m'))\n            return Key::Return;\n        if (c == control('i'))\n            return Key::Tab;\n        if (c == ' ')\n            return Key::Space;\n        if (c == m_original_termios.c_cc[VERASE])\n            return Key::Backspace;\n        if (c == 127) // when it's not backspace\n            return Key::Delete;\n        if (c == 27)\n            return Key::Escape;\n        return c;\n    };\n    auto parse_key = [&convert](unsigned char c) -> Key {\n        if (Codepoint cp = convert(c); cp > 255)\n            return Key{cp};\n        // Special case: you can type NUL with Ctrl-2 or Ctrl-Shift-2 or\n        // Ctrl-Backtick, but the most straightforward way is Ctrl-Space.\n        if (c == 0)\n            return ctrl(Key::Space);\n        // Represent Ctrl-letter combinations in lower-case, to be clear\n        // that Shift is not involved.\n        if (c < 27)\n            return ctrl(c - 1 + 'a');\n        // Represent Ctrl-symbol combinations in \"upper-case\", as they are\n        // traditionally-rendered.\n        // Note that Escape is handled elsewhere.\n        if (c < 32)\n            return ctrl(c - 1 + 'A');\n\n       struct Sentinel{};\n       struct CharIterator\n       {\n           unsigned char operator*() { if (not c) c = get_char().value_or((unsigned char)0); return *c; }\n           CharIterator& operator++() { c.reset(); return *this; }\n           bool operator==(const Sentinel&) const { return false; }\n           Optional<unsigned char> c;\n       };\n       return Key{utf8::codepoint(CharIterator{c}, Sentinel{})};\n    };\n\n    static auto parse_mask = [](int mask) {\n        Key::Modifiers mod = Key::Modifiers::None;\n        if (mask & 1)\n            mod |= Key::Modifiers::Shift;\n        if (mask & 2)\n            mod |= Key::Modifiers::Alt;\n        if (mask & 4)\n            mod |= Key::Modifiers::Control;\n        return mod;\n    };\n\n    auto parse_csi = [this, &convert]() -> Optional<Key> {\n        auto next_char = [] { return get_char().value_or((unsigned char)0xff); };\n        int params[16][4] = {};\n        auto c = next_char();\n        char private_mode = 0;\n        if (c == '?' or c == '<' or c == '=' or c == '>')\n        {\n            private_mode = c;\n            c = next_char();\n        }\n        for (int count = 0, subcount = 0; count < 16 and c >= 0x30 && c <= 0x3f; c = next_char())\n        {\n            if (isdigit(c))\n                params[count][subcount] = params[count][subcount] * 10 + c - '0';\n            else if (c == ':' && subcount < 3)\n                ++subcount;\n            else if (c == ';')\n            {\n                ++count;\n                subcount = 0;\n            }\n            else\n                return {};\n        }\n        if (c != '$' and (c < 0x40 or c > 0x7e))\n            return {};\n\n        auto mouse_button = [this](Key::Modifiers mod, Key::MouseButton button, Codepoint coord, bool release) {\n            auto mask = 1 << (int)button;\n            if (not release)\n            {\n                mod |= (m_mouse_state & mask) ? Key::Modifiers::MousePos : Key::Modifiers::MousePress;\n                m_mouse_state |= mask;\n            }\n            else\n            {\n                mod |= Key::Modifiers::MouseRelease;\n                m_mouse_state &= ~mask;\n            }\n            return Key{mod | Key::to_modifier(button), coord};\n        };\n\n        auto mouse_scroll = [this](Key::Modifiers mod, Codepoint coord, bool down) -> Key {\n            return {mod | Key::Modifiers::Scroll | (Key::Modifiers)((down ? m_wheel_scroll_amount : -1 * m_wheel_scroll_amount) << 16), coord};\n        };\n\n        auto masked_key = [&](Codepoint key, Codepoint shifted_key = 0) {\n            int mask = std::max(params[1][0] - 1, 0);\n            Key::Modifiers modifiers = parse_mask(mask);\n            if (shifted_key != 0 and (modifiers & Key::Modifiers::Shift))\n            {\n                modifiers &= ~Key::Modifiers::Shift;\n                key = shifted_key;\n            }\n            return Key{modifiers, key};\n        };\n\n        switch (c)\n        {\n        case '$':\n            if (private_mode == '?' and next_char() == 'y') // DECRPM\n            {\n                if (params[0][0] == 2026)\n                    m_synchronized.supported = (params[1][0] == 1 or params[1][0] == 2);\n                return Key{Key::Invalid};\n            }\n            switch (params[0][0])\n            {\n            case 23: case 24:\n                return Key{Key::Modifiers::Shift, Key::F11 + params[0][0] - 23}; // rxvt style\n            }\n            return {};\n        case 'A': return masked_key(Key::Up);\n        case 'B': return masked_key(Key::Down);\n        case 'C': return masked_key(Key::Right);\n        case 'D': return masked_key(Key::Left);\n        case 'E': return masked_key('5');        // Numeric keypad 5\n        case 'F': return masked_key(Key::End);   // PC/xterm style\n        case 'H': return masked_key(Key::Home);  // PC/xterm style\n        case 'P': return masked_key(Key::F1);\n        case 'Q': return masked_key(Key::F2);\n        case 'R': return masked_key(Key::F3);\n        case 'S': return masked_key(Key::F4);\n        case '~':\n            switch (params[0][0])\n            {\n            case 1: return masked_key(Key::Home);     // VT220/tmux style\n            case 2: return masked_key(Key::Insert);\n            case 3: return masked_key(Key::Delete);\n            case 4: return masked_key(Key::End);      // VT220/tmux style\n            case 5: return masked_key(Key::PageUp);\n            case 6: return masked_key(Key::PageDown);\n            case 7: return masked_key(Key::Home);     // rxvt style\n            case 8: return masked_key(Key::End);      // rxvt style\n            case 11: case 12: case 13: case 14: case 15:\n                return masked_key(Key::F1 + params[0][0] - 11);\n            case 17: case 18: case 19: case 20: case 21:\n                return masked_key(Key::F6 + params[0][0] - 17);\n            case 23: case 24:\n                return masked_key(Key::F11 + params[0][0] - 23);\n            case 25: case 26:\n                return Key{Key::Modifiers::Shift, Key::F3 + params[0][0] - 25}; // rxvt style\n            case 27:\n                return masked_key(convert(static_cast<Codepoint>(params[2][0])));\n            case 28: case 29:\n                return Key{Key::Modifiers::Shift, Key::F5 + params[0][0] - 28}; // rxvt style\n            case 31: case 32:\n                return Key{Key::Modifiers::Shift, Key::F7 + params[0][0] - 31}; // rxvt style\n            case 33: case 34:\n                return Key{Key::Modifiers::Shift, Key::F9 + params[0][0] - 33}; // rxvt style\n            case 200:\n                m_paste_buffer = String{};\n                return Key{Key::Invalid};\n            case 201:\n                if (m_paste_buffer)\n                {\n                    m_on_paste(*m_paste_buffer);\n                    m_paste_buffer.reset();\n                }\n                return Key{Key::Invalid};\n            }\n            return {};\n        case 'u':\n        {\n            Codepoint key;\n            switch (params[0][0])\n            {\n                // Treat numpad keys the same as their non-numpad counterparts. Could add a numpad modifier here.\n                case 57399: key = '0'; break;\n                case 57400: key = '1'; break;\n                case 57401: key = '2'; break;\n                case 57402: key = '3'; break;\n                case 57403: key = '4'; break;\n                case 57404: key = '5'; break;\n                case 57405: key = '6'; break;\n                case 57406: key = '7'; break;\n                case 57407: key = '8'; break;\n                case 57408: key = '9'; break;\n                case 57409: key = '.'; break;\n                case 57410: key = '/'; break;\n                case 57411: key = '*'; break;\n                case 57412: key = '-'; break;\n                case 57413: key = '+'; break;\n                case 57414: key = Key::Return; break;\n                case 57415: key = '='; break;\n                case 57417: key = Key::Left; break;\n                case 57418: key = Key::Right; break;\n                case 57419: key = Key::Up; break;\n                case 57420: key = Key::Down; break;\n                case 57421: key = Key::PageUp; break;\n                case 57422: key = Key::PageDown; break;\n                case 57423: key = Key::Home; break;\n                case 57424: key = Key::End; break;\n                case 57425: key = Key::Insert; break;\n                case 57426: key = Key::Delete; break;\n                default: key = convert(static_cast<Codepoint>(params[0][0])); break;\n            }\n            return masked_key(key, convert(static_cast<Codepoint>(params[0][1])));\n        }\n        case 'Z': return shift(Key::Tab);\n        case 'I': return {Key::FocusIn};\n        case 'O': return {Key::FocusOut};\n        case 'M': case 'm':\n            const bool sgr = private_mode == '<';\n            if (not sgr and c != 'M')\n                return {};\n\n            const Codepoint b = sgr ? params[0][0] : next_char() - 32;\n            const int x = (sgr ? params[1][0] : next_char() - 32) - 1;\n            const int y = (sgr ? params[2][0] : next_char() - 32) - 1;\n            auto coord = encode_coord({y - content_line_offset(), x});\n            Key::Modifiers mod = parse_mask((b >> 2) & 0x7);\n            switch (const int code = b & 0x43; code)\n            {\n            case 0: case 1: case 2:\n                return mouse_button(mod, Key::MouseButton{code}, coord, c == 'm');\n            case 3:\n                if (sgr)\n                    return {};\n                else if (int guess = ffs(m_mouse_state) - 1; 0 <= guess and guess < 3)\n                    return mouse_button(mod, Key::MouseButton{guess}, coord, true);\n                break;\n            case 64: return mouse_scroll(mod, coord, false);\n            case 65: return mouse_scroll(mod, coord, true);\n            }\n            return Key{Key::Modifiers::MousePos, coord};\n        }\n        return {};\n    };\n\n    static auto parse_ss3 = []() -> Optional<Key> {\n        int raw_mask = 0;\n        char code = '0';\n        do {\n            raw_mask = raw_mask * 10 + (code - '0');\n            code = get_char().value_or((unsigned char)0xff);\n        } while (code >= '0' and code <= '9');\n        Key::Modifiers mod = parse_mask(std::max(raw_mask - 1, 0));\n\n        switch (code)\n        {\n        case ' ': return Key{mod, Key::Space};\n        case 'A': return Key{mod, Key::Up};\n        case 'B': return Key{mod, Key::Down};\n        case 'C': return Key{mod, Key::Right};\n        case 'D': return Key{mod, Key::Left};\n        case 'F': return Key{mod, Key::End};\n        case 'H': return Key{mod, Key::Home};\n        case 'I': return Key{mod, Key::Tab};\n        case 'M': return Key{mod, Key::Return};\n        case 'P': return Key{mod, Key::F1};\n        case 'Q': return Key{mod, Key::F2};\n        case 'R': return Key{mod, Key::F3};\n        case 'S': return Key{mod, Key::F4};\n        case 'X': return Key{mod, '='};\n        case 'j': return Key{mod, '*'};\n        case 'k': return Key{mod, '+'};\n        case 'l': return Key{mod, ','};\n        case 'm': return Key{mod, '-'};\n        case 'n': return Key{mod, '.'};\n        case 'o': return Key{mod, '/'};\n        case 'p': return Key{mod, '0'};\n        case 'q': return Key{mod, '1'};\n        case 'r': return Key{mod, '2'};\n        case 's': return Key{mod, '3'};\n        case 't': return Key{mod, '4'};\n        case 'u': return Key{mod, '5'};\n        case 'v': return Key{mod, '6'};\n        case 'w': return Key{mod, '7'};\n        case 'x': return Key{mod, '8'};\n        case 'y': return Key{mod, '9'};\n        default: return {};\n        }\n    };\n\n    if (*c == 27)\n    {\n        if (auto next = get_char())\n        {\n            if (*next == '[') // potential CSI\n                return parse_csi().value_or(alt('['));\n            if (*next == 'O') // potential SS3\n                return parse_ss3().value_or(alt('O'));\n            return alt(parse_key(*next));\n        }\n        else if (not m_paste_buffer)\n            return Key{Key::Escape};\n    }\n\n    if (m_paste_buffer)\n    {\n        auto paste_convert = [](char c) {\n            switch (c)\n            {\n                case '\\r': return '\\n';\n                default: return c;\n            }\n        };\n\n        m_paste_buffer->push_back(paste_convert(*c));\n        return Key{Key::Invalid};\n    }\n    return parse_key(*c);\n}\n\ntemplate<typename T>\nT div_round_up(T a, T b)\n{\n    return (a - T(1)) / b + T(1);\n}\n\nvoid TerminalUI::draw_menu()\n{\n    // menu show may have not created the window if it did not fit.\n    // so be tolerant.\n    if (not m_menu)\n        return;\n\n    const int item_count = (int)m_menu.items.size();\n    if (m_menu.columns == 0)\n    {\n        const auto win_width = m_menu.size.column - 4;\n        kak_assert(m_menu.size.line == 1);\n        ColumnCount pos = 0;\n\n        m_menu.draw({0, 0}, DisplayAtom(m_menu.first_item > 0 ? \"< \" : \"\"), m_menu.bg);\n\n        int i = m_menu.first_item;\n        for (; i < item_count and pos < win_width; ++i)\n        {\n            const DisplayLine& item = m_menu.items[i];\n            const ColumnCount item_width = item.length();\n            auto& face = i == m_menu.selected_item ? m_menu.fg : m_menu.bg;\n            m_menu.draw({0, pos+2}, item.atoms(), face);\n            if (pos + item_width >= win_width)\n                m_menu.draw({0, win_width+2}, DisplayAtom(\"…\"), m_menu.bg);\n            pos += item_width + 1;\n        }\n\n        if (i != item_count)\n            m_menu.draw({0, win_width+3}, DisplayAtom(\">\"), m_menu.bg);\n\n        m_dirty = true;\n        return;\n    }\n\n    const LineCount menu_lines = div_round_up(item_count, m_menu.columns);\n    const LineCount win_height = m_menu.size.line;\n    kak_assert(win_height <= menu_lines);\n\n    const ColumnCount column_width = (m_menu.size.column - 1) / m_menu.columns;\n\n    const LineCount mark_height = min(div_round_up(sq(win_height), menu_lines),\n                                      win_height);\n\n    const int menu_cols = div_round_up(item_count, (int)m_menu.size.line);\n    const int first_col = m_menu.first_item / (int)m_menu.size.line;\n\n    const LineCount mark_line = (win_height - mark_height) * first_col / max(1, menu_cols - m_menu.columns);\n\n    for (auto line = 0_line; line < win_height; ++line)\n    {\n        for (int col = 0; col < m_menu.columns; ++col)\n        {\n            int item_idx = (first_col + col) * (int)m_menu.size.line + (int)line;\n            auto& face = item_idx < item_count and item_idx == m_menu.selected_item ? m_menu.fg : m_menu.bg;\n            auto atoms = item_idx < item_count ? m_menu.items[item_idx].atoms() : ConstArrayView<DisplayAtom>{};\n            m_menu.draw({line, col * column_width}, atoms, face);\n        }\n        const bool is_mark = line >= mark_line and line < mark_line + mark_height;\n        m_menu.draw({line, m_menu.size.column - 1}, DisplayAtom(is_mark ? \"█\" : \"░\"), m_menu.bg);\n    }\n    m_dirty = true;\n}\n\nstatic LineCount height_limit(MenuStyle style)\n{\n    switch (style)\n    {\n        case MenuStyle::Inline: return 10_line;\n        case MenuStyle::Prompt: return 10_line;\n        case MenuStyle::Search: return 3_line;\n    }\n    kak_assert(false);\n    return 0_line;\n}\n\nvoid TerminalUI::menu_show(ConstArrayView<DisplayLine> items,\n                           DisplayCoord anchor, Face fg, Face bg,\n                           MenuStyle style)\n{\n    if (m_menu)\n    {\n        m_menu.destroy();\n        m_dirty = true;\n    }\n\n    m_menu.fg = fg;\n    m_menu.bg = bg;\n    m_menu.style = style;\n    m_menu.anchor = anchor;\n\n    if (m_dimensions.column <= 2)\n        return;\n\n    const int item_count = items.size();\n    m_menu.items.clear(); // make sure it is empty\n    m_menu.items.reserve(item_count);\n    const auto longest = accumulate(items | transform(&DisplayLine::length),\n                                    1_col, [](auto&& lhs, auto&& rhs) { return std::max(lhs, rhs); });\n\n    const ColumnCount max_width = m_dimensions.column - 1;\n    const bool is_inline = style == MenuStyle::Inline;\n    const bool is_search = style == MenuStyle::Search;\n    m_menu.columns = is_search ? 0 : (is_inline ? 1 : max((int)(max_width / (longest+1)), 1));\n\n    const LineCount max_height = min(height_limit(style), max(anchor.line, m_dimensions.line - anchor.line - 1));\n    const LineCount height = is_search ?\n        1 : (min<LineCount>(max_height, div_round_up(item_count, m_menu.columns)));\n\n    if (height == 0)\n        return;\n\n    const ColumnCount maxlen = (m_menu.columns > 1 and item_count > 1) ?\n        max_width / m_menu.columns - 1 : max_width;\n\n    for (auto& item : items)\n    {\n        m_menu.items.push_back(item);\n        m_menu.items.back().trim(0, maxlen);\n        kak_assert(m_menu.items.back().length() <= maxlen);\n    }\n\n    if (is_inline)\n        anchor.line += content_line_offset();\n\n    LineCount line = anchor.line + 1;\n    ColumnCount column = std::max(0_col, std::min(anchor.column, m_dimensions.column - longest - 1));\n    if (is_search)\n    {\n        line = m_status_on_top ? 0_line : m_dimensions.line;\n        column = m_dimensions.column / 2;\n    }\n    else if (not is_inline)\n        line = m_status_on_top ? 1_line : m_dimensions.line - height;\n    else if (line + height > m_dimensions.line and anchor.line >= height)\n        line = anchor.line - height;\n\n    const auto width = is_search ? m_dimensions.column - m_dimensions.column / 2\n                                 : (is_inline ? min(longest+1, m_dimensions.column)\n                                              : m_dimensions.column);\n    m_menu.create({line, column}, {height, width});\n    m_menu.selected_item = item_count;\n    m_menu.first_item = 0;\n\n    draw_menu();\n\n    if (m_info)\n        info_show(m_info.title, m_info.content,\n                  m_info.anchor, m_info.face, m_info.style);\n}\n\nvoid TerminalUI::menu_select(int selected)\n{\n    const int item_count = m_menu.items.size();\n    if (selected < 0 or selected >= item_count)\n    {\n        m_menu.selected_item = -1;\n        m_menu.first_item = 0;\n    }\n    else if (m_menu.columns == 0) // Do not columnize\n    {\n        m_menu.selected_item = selected;\n        const ColumnCount width = m_menu.size.column - 3;\n        int first = 0;\n        ColumnCount item_col = 0;\n        for (int i = 0; i <= selected; ++i)\n        {\n            const ColumnCount item_width = m_menu.items[i].length() + 1;\n            if (item_col + item_width > width)\n            {\n                first = i;\n                item_col = item_width;\n            }\n            else\n                item_col += item_width;\n        }\n        m_menu.first_item = first;\n    }\n    else\n    {\n        m_menu.selected_item = selected;\n        const int menu_cols = div_round_up(item_count, (int)m_menu.size.line);\n        const int first_col = m_menu.first_item / (int)m_menu.size.line;\n        const int selected_col = m_menu.selected_item / (int)m_menu.size.line;\n        if (selected_col < first_col)\n            m_menu.first_item = selected_col * (int)m_menu.size.line;\n        if (selected_col >= first_col + m_menu.columns)\n            m_menu.first_item = min(selected_col, menu_cols - m_menu.columns) * (int)m_menu.size.line;\n    }\n    draw_menu();\n}\n\nvoid TerminalUI::menu_hide()\n{\n    if (not m_menu)\n        return;\n\n    m_menu.items.clear();\n    m_menu.destroy();\n    m_dirty = true;\n\n    // Recompute info as it does not have to avoid the menu anymore\n    if (m_info)\n        info_show(m_info.title, m_info.content, m_info.anchor, m_info.face, m_info.style);\n}\n\nstatic DisplayCoord compute_pos(DisplayCoord anchor, DisplayCoord size,\n                                TerminalUI::Rect rect, TerminalUI::Rect to_avoid,\n                                bool prefer_above)\n{\n    DisplayCoord pos;\n    if (prefer_above)\n    {\n        pos = anchor - DisplayCoord{size.line};\n        if (pos.line < 0)\n            prefer_above = false;\n    }\n    auto rect_end = rect.pos + rect.size;\n    if (not prefer_above)\n    {\n        pos = anchor + DisplayCoord{1_line};\n        if (pos.line + size.line >= rect_end.line)\n            pos.line = max(rect.pos.line, anchor.line - size.line);\n    }\n    if (pos.column + size.column >= rect_end.column)\n        pos.column = max(rect.pos.column, rect_end.column - size.column);\n\n    if (to_avoid.size != DisplayCoord{})\n    {\n        DisplayCoord to_avoid_end = to_avoid.pos + to_avoid.size;\n\n        DisplayCoord end = pos + size;\n\n        // check intersection\n        if (not (end.line < to_avoid.pos.line or end.column < to_avoid.pos.column or\n                 pos.line > to_avoid_end.line or pos.column > to_avoid_end.column))\n        {\n            pos.line = min(to_avoid.pos.line, anchor.line) - size.line;\n            // if above does not work, try below\n            if (pos.line < 0)\n                pos.line = max(to_avoid_end.line, anchor.line);\n        }\n    }\n\n    return pos;\n}\n\nstatic DisplayLineList wrap_lines(const DisplayLineList& lines, ColumnCount max_width)\n{\n    DisplayLineList result;\n    for (auto line : lines)\n    {\n        ColumnCount column = 0;\n        for (auto it = line.begin(); it != line.end(); )\n        {\n            auto length = it->length();\n            column += length;\n            if (column > max_width)\n            {\n                auto content = it->content().substr(0, length - (column - max_width));\n                auto pos = find_if(content | reverse(), [](char c) { return not is_word(c); });\n                if (pos != content.rend())\n                    content = {content.begin(), pos.base()};\n\n                if (not content.empty())\n                    it = ++line.split(it, content.column_length());\n                result.push_back(AtomList(std::make_move_iterator(line.begin()),\n                                          std::make_move_iterator(it)));\n                it = line.erase(line.begin(), it);\n                column = 0;\n            }\n            else\n                ++it;\n        }\n        result.push_back(std::move(line));\n    }\n    return result;\n}\n\nvoid TerminalUI::info_show(const DisplayLine& title, const DisplayLineList& content,\n                           DisplayCoord anchor, Face face, InfoStyle style)\n{\n    info_hide();\n\n    m_info.title = title;\n    m_info.content = content;\n    m_info.anchor = anchor;\n    m_info.face = face;\n    m_info.style = style;\n\n    const bool framed = style == InfoStyle::Prompt or style == InfoStyle::Modal;\n    const bool assisted = style == InfoStyle::Prompt and m_assistant.size() != 0;\n\n    DisplayCoord max_size = m_dimensions;\n    if (style == InfoStyle::MenuDoc)\n        max_size.column = std::max(m_dimensions.column - (m_menu.pos.column + m_menu.size.column),\n                                   m_menu.pos.column);\n    else if (style != InfoStyle::Modal)\n        max_size.line -= m_menu.size.line;\n\n    const auto max_content_width = (m_info_max_width > 0 ? std::min(max_size.column, m_info_max_width) : max_size.column) -\n                                   (framed ? 4 : 0) -\n                                   (assisted ? m_assistant[0].column_length() : 0);\n    if (max_content_width <= 0)\n        return;\n\n    auto compute_size = [](const DisplayLineList& lines) -> DisplayCoord {\n        return {(int)lines.size(), accumulate(lines, 0_col, [](ColumnCount c, const DisplayLine& l) { return std::max(c, l.length()); })};\n    };\n\n    DisplayCoord content_size = compute_size(content);\n    const bool wrap = content_size.column > max_content_width;\n    DisplayLineList wrapped_content;\n    if (wrap)\n    {\n        wrapped_content = wrap_lines(content, max_content_width);\n        content_size = compute_size(wrapped_content);\n    }\n    const auto& lines = wrap ? wrapped_content : content;\n\n    DisplayCoord size{content_size.line, std::max(content_size.column, title.length() + (framed ? 2 : 0))};\n    if (framed)\n        size += {2, 4};\n    if (assisted)\n        size = {std::max(LineCount{(int)m_assistant.size()-1}, size.line), size.column + m_assistant[0].column_length()};\n    size = {std::min(max_size.line, size.line), std::min(max_size.column, size.column)};\n\n    if ((framed and size.line < 3) or size.line <= 0)\n        return;\n\n    const Rect rect = {content_line_offset(), m_dimensions};\n    if (style == InfoStyle::Prompt)\n    {\n        anchor = DisplayCoord{m_status_on_top ? 0 : m_dimensions.line, m_dimensions.column-1};\n        anchor = compute_pos(anchor, size, rect, m_menu, style == InfoStyle::InlineAbove);\n    }\n    else if (style == InfoStyle::Modal)\n    {\n        auto half = [](const DisplayCoord& c) { return DisplayCoord{c.line / 2, c.column / 2}; };\n        anchor = rect.pos + half(rect.size) - half(size);\n    }\n    else if (style == InfoStyle::MenuDoc)\n    {\n        const auto right_max_width = m_dimensions.column - (m_menu.pos.column + m_menu.size.column);\n        const auto left_max_width = m_menu.pos.column;\n        anchor.line = m_menu.pos.line;\n        if (size.column <= right_max_width or right_max_width >= left_max_width)\n            anchor.column = m_menu.pos.column + m_menu.size.column;\n        else\n            anchor.column = m_menu.pos.column - size.column;\n    }\n    else\n    {\n        anchor = compute_pos(anchor, size, rect, m_menu, style == InfoStyle::InlineAbove);\n        anchor.line += content_line_offset();\n    }\n\n    m_info.create(anchor, size);\n    for (auto line = 0_line; line < size.line; ++line)\n    {\n        auto draw_atoms = [&, this, pos=DisplayCoord{line}](auto&&... args) mutable {\n            auto draw = overload(\n                [&](ColumnCount padding) {\n                    pos.column += padding;\n                },\n                [&](String str) {\n                    auto len = str.column_length();\n                    m_info.draw(pos, DisplayAtom{std::move(str)}, face);\n                    pos.column += len;\n                },\n                [&](const DisplayLine& atoms) {\n                    m_info.draw(pos, atoms.atoms(), face);\n                    pos.column += atoms.length();\n                });\n            (draw(args), ...);\n        };\n\n        constexpr Codepoint dash{L'─'};\n        constexpr Codepoint dotted_dash{L'┄'};\n        if (assisted)\n        {\n            const auto assistant_top_margin = (size.line - m_assistant.size()+1) / 2;\n            StringView assistant_line = (line >= assistant_top_margin) ?\n                m_assistant[(int)min(line - assistant_top_margin, LineCount{(int)m_assistant.size()}-1)]\n              : m_assistant[(int)m_assistant.size()-1];\n\n            draw_atoms(assistant_line.str());\n        }\n        if (not framed)\n            draw_atoms(lines[(int)line]);\n        else if (line == 0)\n        {\n            if (title.atoms().empty() or content_size.column < 2)\n                draw_atoms(\"╭─\" + String{dash, content_size.column} + \"─╮\");\n            else\n            {\n                auto trimmed_title = title;\n                trimmed_title.trim(0, content_size.column - 2);\n                auto dash_count = content_size.column - trimmed_title.length() - 2;\n                String left{dash, dash_count / 2};\n                String right{dash, dash_count - dash_count / 2};\n                draw_atoms(\"╭─\" + left + \"┤\", trimmed_title, \"├\" + right +\"─╮\");\n            }\n        }\n        else if (line < size.line - 1 and line <= lines.size())\n        {\n            auto info_line = lines[(int)line - 1];\n            const bool trimmed = info_line.trim(0, content_size.column);\n            const ColumnCount padding = content_size.column - info_line.length();\n            draw_atoms(\"│ \", info_line, padding, (trimmed ? \"…│\" : \" │\"));\n        }\n        else if (line == std::min<LineCount>((int)lines.size() + 1, size.line - 1))\n            draw_atoms(\"╰─\", String(line > lines.size() ? dash : dotted_dash, content_size.column), \"─╯\");\n    }\n    m_dirty = true;\n}\n\nvoid TerminalUI::info_hide()\n{\n    if (not m_info)\n        return;\n    m_info.destroy();\n    m_dirty = true;\n}\n\nvoid TerminalUI::set_on_key(OnKeyCallback callback)\n{\n    m_on_key = std::move(callback);\n    EventManager::instance().force_signal(0);\n}\n\nvoid TerminalUI::set_on_paste(OnPasteCallback callback)\n{\n    m_on_paste = std::move(callback);\n}\n\nDisplayCoord TerminalUI::dimensions()\n{\n    return m_dimensions;\n}\n\nLineCount TerminalUI::content_line_offset() const\n{\n    return m_status_on_top ? 1 : 0;\n}\n\nvoid TerminalUI::set_resize_pending()\n{\n    m_resize_pending = true;\n    EventManager::instance().force_signal(0);\n}\n\nvoid TerminalUI::setup_terminal()\n{\n    write(STDOUT_FILENO,\n        \"\\033[?1049h\" // enable alternative screen buffer\n        \"\\033[?1004h\" // enable focus notify\n        \"\\033[>4;1m\"  // request CSI u style key reporting\n        \"\\033[>5u\"    // kitty progressive enhancement - report shifted key codes\n        \"\\033[22t\"    // save the current window title\n        \"\\033[?25l\"   // hide cursor\n        \"\\033=\"       // set application keypad mode, so the keypad keys send unique codes\n        \"\\033[?2004h\" // force enable bracketed-paste events\n    );\n}\n\nvoid TerminalUI::restore_terminal()\n{\n    write(STDOUT_FILENO,\n        \"\\033>\"\n        \"\\033[?25h\"\n        \"\\033[23t\"\n        \"\\033[<u\"\n        \"\\033[>4;0m\"\n        \"\\033[?1004l\"\n        \"\\033[?1049l\"\n        \"\\033[?2004l\"\n        \"\\033[m\" // set the terminal output back to default colours and style\n    );\n}\n\nvoid TerminalUI::enable_mouse(bool enabled)\n{\n    if (enabled == m_mouse_enabled)\n        return;\n\n    m_mouse_enabled = enabled;\n    if (enabled)\n    {\n        write(STDOUT_FILENO,\n            \"\\033[?1006h\" // force SGR mode\n            \"\\033[?1000h\" // enable mouse\n            \"\\033[?1002h\" // force enable report mouse position\n        );\n    }\n    else\n    {\n        write(STDOUT_FILENO,\n            \"\\033[?1002l\"\n            \"\\033[?1000l\"\n            \"\\033[?1006l\"\n        );\n    }\n}\n\nvoid TerminalUI::set_ui_options(const Options& options)\n{\n    auto find = [&](StringView name) -> Optional<StringView> {\n        if (auto it = options.find(name); it != options.end())\n            return StringView{it->value};\n        return {};\n    };\n\n    auto assistant = find(\"terminal_assistant\").value_or(\"clippy\");\n    if (assistant == \"clippy\")\n        m_assistant = assistant_clippy;\n    else if (assistant == \"cat\")\n        m_assistant = assistant_cat;\n    else if (assistant == \"dilbert\")\n        m_assistant = assistant_dilbert;\n    else if (assistant == \"none\" or assistant == \"off\")\n        m_assistant = ConstArrayView<StringView>{};\n\n    auto to_bool = [](StringView s) { return s == \"yes\" or s == \"true\"; };\n\n    m_status_on_top = find(\"terminal_status_on_top\").map(to_bool).value_or(false);\n    m_set_title = find(\"terminal_set_title\").map(to_bool).value_or(true);\n    m_title = find(\"terminal_title\").map([](StringView s) { return String{s}; });\n\n    auto synchronized = find(\"terminal_synchronized\").map(to_bool);\n    m_synchronized.set = (bool)synchronized;\n    m_synchronized.requested = synchronized.value_or(false);\n    if (not m_synchronized.queried and not m_synchronized.set) {\n        write(STDOUT_FILENO, \"\\033[?2026$p\");\n        m_synchronized.queried = true;\n    }\n\n    m_shift_function_key = find(\"terminal_shift_function_key\").map(str_to_int_ifp).value_or(default_shift_function_key);\n\n    enable_mouse(find(\"terminal_enable_mouse\").map(to_bool).value_or(true));\n    m_wheel_scroll_amount = find(\"terminal_wheel_scroll_amount\").map(str_to_int_ifp).value_or(3);\n\n    m_padding_char = find(\"terminal_padding_char\").map([](StringView s) { return s.column_length() < 1 ? ' ' : s[0_char]; }).value_or(Codepoint{'~'});\n    m_padding_fill = find(\"terminal_padding_fill\").map(to_bool).value_or(false);\n    \n    bool new_cursor_native = find(\"terminal_cursor_native\").map(to_bool).value_or(false);\n    if (new_cursor_native != m_cursor_native)\n    {\n        m_cursor_native = new_cursor_native;\n        write(STDOUT_FILENO, m_cursor_native ? \"\\033[?25h\" : \"\\033[?25l\");\n    }\n\n    m_info_max_width = find(\"terminal_info_max_width\").map(str_to_int_ifp).value_or(0);\n}\n\n}\n"
  },
  {
    "path": "src/terminal_ui.hh",
    "content": "#ifndef terminal_hh_INCLUDED\n#define terminal_hh_INCLUDED\n\n#include \"array_view.hh\"\n#include \"coord.hh\"\n#include \"display_buffer.hh\"\n#include \"event_manager.hh\"\n#include \"face.hh\"\n#include \"optional.hh\"\n#include \"string.hh\"\n#include \"user_interface.hh\"\n#include \"unique_ptr.hh\"\n\n#include <termios.h>\n\nnamespace Kakoune\n{\n\nstruct DisplayAtom;\nstruct Writer;\n\nclass TerminalUI : public UserInterface, public Singleton<TerminalUI>\n{\npublic:\n    TerminalUI();\n    ~TerminalUI() override;\n\n    TerminalUI(const TerminalUI&) = delete;\n    TerminalUI& operator=(const TerminalUI&) = delete;\n\n    bool is_ok() const override { return (bool)m_window; }\n\n    void draw(const DisplayBuffer& display_buffer,\n              DisplayCoord cursor_pos,\n              const Face& default_face,\n              const Face& padding_face,\n              ColumnCount widget_columns) override;\n\n    void draw_status(const DisplayLine& prompt,\n                     const DisplayLine& content,\n                     const ColumnCount cursor_pos,\n                     const DisplayLine& mode_line,\n                     const Face& default_face) override;\n\n    void menu_show(ConstArrayView<DisplayLine> items,\n                   DisplayCoord anchor, Face fg, Face bg,\n                   MenuStyle style) override;\n    void menu_select(int selected) override;\n    void menu_hide() override;\n\n    void info_show(const DisplayLine& title, const DisplayLineList& content,\n                   DisplayCoord anchor, Face face,\n                   InfoStyle style) override;\n    void info_hide() override;\n\n    void refresh(bool force) override;\n\n    DisplayCoord dimensions() override;\n    void set_on_key(OnKeyCallback callback) override;\n    void set_on_paste(OnPasteCallback callback) override;\n    void set_ui_options(const Options& options) override;\n\n    static void setup_terminal();\n    static void restore_terminal();\n\n    void suspend();\n\n    struct Rect\n    {\n        DisplayCoord pos;\n        DisplayCoord size;\n    };\n\nprivate:\n    void check_resize(bool force = false);\n    void redraw(bool force);\n\n    Optional<Key> get_next_key();\n\n    struct Window : Rect\n    {\n        void create(const DisplayCoord& pos, const DisplayCoord& size);\n        void destroy();\n        void blit(Window& target);\n        void draw(DisplayCoord pos, ConstArrayView<DisplayAtom> atoms, const Face& default_face);\n\n        explicit operator bool() const { return (bool)lines; }\n\n        struct Line;\n        UniquePtr<Line[]> lines;\n    };\n\n    struct Screen : Window\n    {\n        void output(bool force, bool synchronized, Writer& writer);\n        void set_face(const Face& face, Writer& writer);\n\n        UniquePtr<size_t[]> hashes;\n        Face m_active_face;\n    };\n\n    Window m_window;\n    Screen m_screen;\n\n    DisplayCoord m_dimensions;\n    termios m_original_termios{};\n\n    void set_raw_mode() const;\n\n    struct Menu : Window\n    {\n        Vector<DisplayLine, MemoryDomain::Display> items;\n        Face fg;\n        Face bg;\n        DisplayCoord anchor;\n        MenuStyle style;\n        int selected_item = 0;\n        int first_item = 0;\n        int columns = 1;\n    } m_menu;\n\n    void draw_menu();\n\n    LineCount content_line_offset() const;\n\n    struct Info : Window\n    {\n        DisplayLine title;\n        DisplayLineList content;\n        Face face;\n        DisplayCoord anchor;\n        InfoStyle style;\n    } m_info;\n\n    DisplayCoord m_cursor_pos;\n\n    FDWatcher m_stdin_watcher;\n    OnKeyCallback m_on_key;\n    OnPasteCallback m_on_paste;\n    Optional<String> m_paste_buffer;\n\n    bool m_status_on_top = false;\n    ConstArrayView<StringView> m_assistant;\n\n    void enable_mouse(bool enabled);\n\n    bool m_mouse_enabled = false;\n    int m_wheel_scroll_amount = 3;\n    int m_mouse_state = 0;\n\n    static constexpr int default_shift_function_key = 12;\n    int m_shift_function_key = default_shift_function_key;\n\n    bool m_set_title = true;\n    Optional<String> m_title;\n\n    struct Synchronized\n    {\n        bool queried : 1;\n        bool supported : 1;\n        bool set : 1;\n        bool requested : 1;\n\n        explicit operator bool() const { return set ? requested : supported; }\n    } m_synchronized{};\n\n    Codepoint m_padding_char = '~';\n    bool m_padding_fill = false;\n    bool m_cursor_native = false;\n\n    bool m_dirty = false;\n\n    bool m_resize_pending = false;\n    void set_resize_pending();\n\n    ColumnCount m_status_len = 0;\n    ColumnCount m_status_pos = 0;\n    ColumnCount m_status_cursor_pos = 0;\n    ColumnCount m_info_max_width = 0;\n};\n\n}\n\n#endif // terminal_hh_INCLUDED\n"
  },
  {
    "path": "src/unicode.hh",
    "content": "#ifndef unicode_hh_INCLUDED\n#define unicode_hh_INCLUDED\n\n#include <cwctype>\n#include <cwchar>\n\n#include \"array_view.hh\"\n#include \"units.hh\"\n\nnamespace Kakoune\n{\n\nusing Codepoint = char32_t;\n\ninline bool is_eol(Codepoint c) noexcept\n{\n    return c == '\\n';\n}\n\ninline bool is_horizontal_blank(Codepoint c) noexcept\n{\n    // Characters considered whitespace by ECMA Regex Spec\n    //  minus vertical tab\n    // <https://262.ecma-international.org/11.0/#sec-white-space>\n    return c == '\\t'      or\n           c == '\\f'      or\n           c == ' '       or\n           c == U'\\u00A0' or\n           c == U'\\uFEFF' or\n           c == U'\\u1680' or\n           c == U'\\u2000' or\n           c == U'\\u2001' or\n           c == U'\\u2002' or\n           c == U'\\u2003' or\n           c == U'\\u2004' or\n           c == U'\\u2005' or\n           c == U'\\u2006' or\n           c == U'\\u2007' or\n           c == U'\\u2008' or\n           c == U'\\u2009' or\n           c == U'\\u200A' or\n           c == U'\\u2028' or\n           c == U'\\u2029' or\n           c == U'\\u202F' or\n           c == U'\\u205F' or\n           c == U'\\u3000' ;\n}\n\ninline bool is_blank(Codepoint c) noexcept\n{\n    // Characters considered Line Terminators by ECMA Regex Spec\n    //  plus vertical tab\n    // <https://262.ecma-international.org/11.0/#sec-line-terminators>\n    return c == '\\n'              or\n           c == '\\r'              or\n           c == '\\v'              or\n           c == U'\\u2028'         or\n           c == U'\\u2029'         or\n           is_horizontal_blank(c) ;\n}\n\ninline bool is_basic_alpha(Codepoint c) noexcept\n{\n    return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z');\n}\n\ninline bool is_basic_digit(Codepoint c) noexcept\n{\n    return c >= '0' and c <= '9';\n}\n\nenum WordType { Word, WORD };\n\ntemplate<WordType word_type = Word>\ninline bool is_word(Codepoint c, ConstArrayView<Codepoint> extra_word_chars = {'_'}) noexcept\n{\n    if (c < 128 ? is_basic_alpha(c) or is_basic_digit(c) : iswalnum((wchar_t)c))\n        return true;\n    for (auto cp : extra_word_chars)\n        if (c == cp)\n            return true;\n    return false;\n}\n\ntemplate<>\ninline bool is_word<WORD>(Codepoint c, ConstArrayView<Codepoint>) noexcept\n{\n    return not is_blank(c);\n}\n\ninline bool is_punctuation(Codepoint c, ConstArrayView<Codepoint> extra_word_chars = {'_'}) noexcept\n{\n    return not (is_word(c, extra_word_chars) or is_blank(c));\n}\n\ninline bool is_identifier(Codepoint c) noexcept\n{\n    return is_basic_alpha(c) or is_basic_digit(c) or\n           c == '_' or c == '-';\n}\n\ninline ColumnCount codepoint_width(Codepoint c) noexcept\n{\n    if (c < 0x80) [[likely]]\n        return 1;\n    const auto width = wcwidth((wchar_t)c);\n    return width >= 0 ? width : 1;\n}\n\nenum class CharCategories\n{\n    Blank,\n    EndOfLine,\n    Word,\n    Punctuation,\n};\n\ntemplate<WordType word_type = Word>\ninline CharCategories categorize(Codepoint c, ConstArrayView<Codepoint> extra_word_chars) noexcept\n{\n    if (is_eol(c))\n        return CharCategories::EndOfLine;\n    if (is_horizontal_blank(c))\n        return CharCategories::Blank;\n    if (word_type == WORD or is_word(c, extra_word_chars))\n        return CharCategories::Word;\n    return CharCategories::Punctuation;\n}\n\ninline char to_lower(char c) noexcept { return c >= 'A' and c <= 'Z' ? c - 'A' + 'a' : c; }\ninline char to_upper(char c) noexcept { return c >= 'a' and c <= 'z' ? c - 'a' + 'A' : c; }\n\ninline bool is_lower(char c) noexcept { return c >= 'a' and c <= 'z'; }\ninline bool is_upper(char c) noexcept { return c >= 'A' and c <= 'Z'; }\n\ninline Codepoint to_lower(Codepoint cp) noexcept { return cp < 128 ? (Codepoint)to_lower((char)cp) : towlower((wchar_t)cp); }\ninline Codepoint to_upper(Codepoint cp) noexcept { return cp < 128 ? (Codepoint)to_upper((char)cp) : towupper((wchar_t)cp); }\n\ninline bool is_lower(Codepoint cp) noexcept { return cp < 128 ? is_lower((char)cp) : iswlower((wchar_t)cp); }\ninline bool is_upper(Codepoint cp) noexcept { return cp < 128 ? is_upper((char)cp) : iswupper((wchar_t)cp); }\n\n}\n\n#endif // unicode_hh_INCLUDED\n"
  },
  {
    "path": "src/unique_descriptor.hh",
    "content": "#ifndef fd_hh_INCLUDED\n#define fd_hh_INCLUDED\n\n#include <utility>\n\nnamespace Kakoune\n{\n\ntemplate<auto close_fn>\nstruct UniqueDescriptor\n{\n    UniqueDescriptor(int descriptor = -1) : descriptor{descriptor} {}\n    UniqueDescriptor(UniqueDescriptor&& other) : descriptor{other.descriptor} { other.descriptor = -1; }\n    UniqueDescriptor& operator=(UniqueDescriptor&& other) { std::swap(descriptor, other.descriptor); other.close(); return *this; }\n    ~UniqueDescriptor() { close(); }\n\n    explicit operator int() const { return descriptor; }\n    explicit operator bool() const { return descriptor != -1; }\n    void close() { if (descriptor != -1) { close_fn(descriptor); descriptor = -1; } }\n    int descriptor;\n};\n\n}\n\n#endif // fd_hh_INCLUDED\n"
  },
  {
    "path": "src/unique_ptr.hh",
    "content": "#ifndef unique_ptr_hh_INCLUDED\n#define unique_ptr_hh_INCLUDED\n\n#include <utility>\n#include <type_traits>\n#include <cstddef>\n\nnamespace Kakoune\n{\n\ntemplate<typename T>\nclass UniquePtr\n{\n    using Type = std::remove_extent_t<T>;\n\npublic:\n    explicit UniquePtr(Type* ptr = nullptr) : m_ptr{ptr} {}\n    UniquePtr(std::nullptr_t) : m_ptr{nullptr} {}\n\n    UniquePtr(const UniquePtr&) = delete;\n    UniquePtr& operator=(const UniquePtr&) = delete;\n\n    template<typename U> requires std::is_convertible_v<U*, T*>\n    UniquePtr(UniquePtr<U>&& other) noexcept { m_ptr = other.release(); }\n\n    template<typename U> requires std::is_convertible_v<U*, T*>\n    UniquePtr& operator=(UniquePtr<U>&& other) noexcept(noexcept(std::declval<Type>().~Type()))\n    {\n        destroy();\n        m_ptr = other.release();\n        return *this;\n    }\n    ~UniquePtr() { destroy(); }\n\n    Type* get() const { return m_ptr; }\n    Type* operator->() const { return m_ptr; };\n    Type& operator*() const { return *m_ptr; };\n    Type& operator[](size_t i) const requires std::is_array_v<T> { return m_ptr[i]; }\n    Type* release() { auto ptr = m_ptr; m_ptr = nullptr; return ptr; }\n\n    void reset(Type* ptr = nullptr) { destroy(); m_ptr = ptr; }\n\n    explicit operator bool() const { return m_ptr != nullptr; }\n\n    friend bool operator==(const UniquePtr&, const UniquePtr&) = default;\n    friend bool operator==(const UniquePtr& lhs, const Type* rhs) { return lhs.get() == rhs; }\n\nprivate:\n    void destroy()\n    {\n        if constexpr (std::is_array_v<T>)\n            delete[] m_ptr;\n        else\n            delete m_ptr;\n        m_ptr = nullptr;\n    }\n\n    Type* m_ptr;\n};\n\ntemplate<typename T, typename... Args>\nUniquePtr<T> make_unique_ptr(Args&&... args)\n{\n    return UniquePtr<T>(new T(std::forward<Args>(args)...));\n}\n\n}\n\n#endif // unique_ptr_hh_INCLUDED\n"
  },
  {
    "path": "src/unit_tests.cc",
    "content": "#include \"unit_tests.hh\"\n\n#include \"assert.hh\"\n#include \"diff.hh\"\n#include \"utf8.hh\"\n#include \"string.hh\"\n\nnamespace Kakoune\n{\n\nUnitTest test_utf8{[]()\n{\n    StringView str = \"maïs mélange bientôt\";\n    kak_assert(utf8::distance(std::begin(str), std::end(str)) == 20);\n    kak_assert(utf8::codepoint(std::begin(str) + 2, std::end(str)) == 0x00EF);\n}};\n\nUnitTest test_diff{[]()\n{\n    struct Diff{DiffOp op; int len;};\n    auto check_diff = [](StringView a, StringView b, std::initializer_list<Diff> diffs) {\n        size_t count = 0;\n        for_each_diff(a.begin(), (int)a.length(), b.begin(), (int)b.length(),\n                      [&](DiffOp op, int len) {\n                          kak_assert(count < diffs.size());\n                          auto& d = diffs.begin()[count++];\n                          kak_assert(d.op == op and d.len == len);\n                      });\n        kak_assert(count == diffs.size());\n    };\n    check_diff(\"a?\", \"!\", {{DiffOp::Remove, 1}, {DiffOp::Add, 1}, {DiffOp::Remove, 1}});\n    check_diff(\"abcde\", \"cd\", {{DiffOp::Remove, 2}, {DiffOp::Keep, 2}, {DiffOp::Remove, 1}});\n    check_diff(\"abcd\", \"cdef\", {{DiffOp::Remove, 2}, {DiffOp::Keep, 2}, {DiffOp::Add, 2}});\n\n    check_diff(\"mais que fais la police\", \"mais ou va la police\",\n               {{DiffOp::Keep, 5}, {DiffOp::Remove, 1}, {DiffOp::Add, 1}, {DiffOp::Keep, 1},\n                {DiffOp::Remove, 1}, {DiffOp::Keep, 1}, {DiffOp::Add, 1}, {DiffOp::Remove, 1},\n                {DiffOp::Keep, 1}, {DiffOp::Remove, 2}, {DiffOp::Keep, 10}} );\n\n    check_diff(\"abcdefghijk\",  \"1cdef2hij34\",\n               {{DiffOp::Remove, 2}, {DiffOp::Add, 1}, {DiffOp::Keep, 4}, {DiffOp::Remove, 1},\n                {DiffOp::Add, 1}, {DiffOp::Keep, 3}, {DiffOp::Add, 2}, {DiffOp::Remove, 1}});\n\n}};\n\n#ifdef KAK_DEBUG\nUnitTest* UnitTest::list = nullptr;\n\nvoid UnitTest::run_all_tests()\n{\n    for (const UnitTest* test = UnitTest::list; test; test = test->next)\n        test->func();\n}\n#endif\n\n}\n"
  },
  {
    "path": "src/unit_tests.hh",
    "content": "#ifndef unit_tests_hh_INCLUDED\n#define unit_tests_hh_INCLUDED\n\nnamespace Kakoune\n{\n\nstruct UnitTest\n{\n#ifdef KAK_DEBUG\n    UnitTest(void (*func)()) : func(func), next(list) { list = this; }\n    void (*func)();\n    const UnitTest* next;\n\n    static void run_all_tests();\n    static UnitTest* list;\n#else\n    UnitTest(void (*func)()) {}\n#endif\n};\n\n}\n\n#endif // unit_tests_hh_INCLUDED\n"
  },
  {
    "path": "src/units.hh",
    "content": "#ifndef units_hh_INCLUDED\n#define units_hh_INCLUDED\n\n#include \"assert.hh\"\n#include \"hash.hh\"\n\n#include <type_traits>\n\nnamespace Kakoune\n{\n\ntemplate<typename RealType, typename ValueType = int>\nclass StronglyTypedNumber\n{\npublic:\n    StronglyTypedNumber() = default;\n\n    [[gnu::always_inline]]\n    constexpr StronglyTypedNumber(ValueType value)\n        : m_value(value)\n    {\n        static_assert(std::is_base_of<StronglyTypedNumber, RealType>::value,\n                     \"RealType is not derived from StronglyTypedNumber\");\n    }\n\n    [[gnu::always_inline]]\n    constexpr friend RealType operator+(RealType lhs, RealType rhs)\n    { return RealType(lhs.m_value + rhs.m_value); }\n\n    [[gnu::always_inline]]\n    constexpr friend RealType operator-(RealType lhs, RealType rhs)\n    { return RealType(lhs.m_value - rhs.m_value); }\n\n    [[gnu::always_inline]]\n    constexpr friend RealType operator*(RealType lhs, RealType rhs)\n    { return RealType(lhs.m_value * rhs.m_value); }\n\n    [[gnu::always_inline]]\n    constexpr friend RealType operator/(RealType lhs, RealType rhs)\n    { return RealType(lhs.m_value / rhs.m_value); }\n\n    [[gnu::always_inline]]\n    RealType& operator+=(RealType other)\n    { m_value += other.m_value; return static_cast<RealType&>(*this); }\n\n    [[gnu::always_inline]]\n    RealType& operator-=(RealType other)\n    { m_value -= other.m_value; return static_cast<RealType&>(*this); }\n\n    [[gnu::always_inline]]\n    RealType& operator*=(RealType other)\n    { m_value *= other.m_value; return static_cast<RealType&>(*this); }\n\n    [[gnu::always_inline]]\n    RealType& operator/=(RealType other)\n    { m_value /= other.m_value; return static_cast<RealType&>(*this); }\n\n    [[gnu::always_inline]]\n    RealType& operator++()\n    { ++m_value; return static_cast<RealType&>(*this); }\n\n    [[gnu::always_inline]]\n    RealType& operator--()\n    { --m_value; return static_cast<RealType&>(*this); }\n\n    [[gnu::always_inline]]\n    RealType operator++(int)\n    { RealType backup(static_cast<RealType&>(*this)); ++m_value; return backup; }\n\n    [[gnu::always_inline]]\n    RealType operator--(int)\n    { RealType backup(static_cast<RealType&>(*this)); --m_value; return backup; }\n\n    [[gnu::always_inline]]\n    constexpr RealType operator-() const { return RealType(-m_value); }\n\n    [[gnu::always_inline]]\n    constexpr friend RealType operator%(RealType lhs, RealType rhs)\n    { return RealType(lhs.m_value % rhs.m_value); }\n\n    [[gnu::always_inline]]\n    RealType& operator%=(RealType other)\n    { m_value %= other.m_value; return static_cast<RealType&>(*this); }\n\n    constexpr friend bool operator==(StronglyTypedNumber lhs, StronglyTypedNumber rhs) = default;\n    constexpr friend auto operator<=>(StronglyTypedNumber lhs, StronglyTypedNumber rhs) = default;\n\n    [[gnu::always_inline]]\n    constexpr bool operator!() const\n    { return not m_value; }\n\n    [[gnu::always_inline]]\n    explicit constexpr operator ValueType() const { return m_value; }\n    [[gnu::always_inline]]\n    explicit constexpr operator bool() const { return m_value; }\n\n    friend constexpr size_t hash_value(RealType val) { return hash_value(val.m_value); }\n    friend constexpr size_t abs(RealType val) { return val.m_value < ValueType(0) ? -val.m_value : val.m_value; }\n\n    explicit operator size_t() { kak_assert(m_value >= 0); return (size_t)m_value; }\n\nprotected:\n    ValueType m_value;\n};\n\nstruct LineCount : public StronglyTypedNumber<LineCount, int>\n{\n    using StronglyTypedNumber::StronglyTypedNumber;\n};\n\n[[gnu::always_inline]]\ninline constexpr LineCount operator\"\"_line(unsigned long long int value)\n{\n    return LineCount(value);\n}\n\nstruct ByteCount : public StronglyTypedNumber<ByteCount, int>\n{\n    using StronglyTypedNumber::StronglyTypedNumber;\n};\n\n[[gnu::always_inline]]\ninline constexpr ByteCount operator\"\"_byte(unsigned long long int value)\n{\n    return ByteCount(value);\n}\n\ntemplate<typename Byte>\n    requires (std::is_same_v<std::remove_cv_t<Byte>, char> or std::is_same_v<std::remove_cv_t<Byte>, void>)\nByte* operator+(Byte* ptr, ByteCount count) { return ptr + (int)count; }\n\nstruct CharCount : public StronglyTypedNumber<CharCount, int>\n{\n    using StronglyTypedNumber::StronglyTypedNumber;\n};\n\n[[gnu::always_inline]]\ninline constexpr CharCount operator\"\"_char(unsigned long long int value)\n{\n    return CharCount(value);\n}\n\nstruct ColumnCount : public StronglyTypedNumber<ColumnCount, int>\n{\n    using StronglyTypedNumber::StronglyTypedNumber;\n};\n\n[[gnu::always_inline]]\ninline constexpr ColumnCount operator\"\"_col(unsigned long long int value)\n{\n    return ColumnCount(value);\n}\n\n}\n\n#endif // units_hh_INCLUDED\n"
  },
  {
    "path": "src/user_interface.hh",
    "content": "#ifndef user_interface_hh_INCLUDED\n#define user_interface_hh_INCLUDED\n\n#include \"array_view.hh\"\n#include \"hash_map.hh\"\n#include \"function.hh\"\n\nnamespace Kakoune\n{\n\nclass String;\nclass DisplayBuffer;\nclass DisplayLine;\nusing DisplayLineList = Vector<DisplayLine, MemoryDomain::Display>;\nstruct DisplayCoord;\nstruct Face;\nstruct Key;\n\nenum class MenuStyle\n{\n    Prompt,\n    Search,\n    Inline\n};\n\nenum class InfoStyle\n{\n    Prompt,\n    Inline,\n    InlineAbove,\n    InlineBelow,\n    MenuDoc,\n    Modal\n};\n\nenum class EventMode;\n\nusing OnKeyCallback = Function<void(Key key)>;\nusing OnPasteCallback = Function<void(StringView content)>;\n\nclass UserInterface\n{\npublic:\n    virtual ~UserInterface() = default;\n\n    virtual bool is_ok() const = 0;\n\n    virtual void menu_show(ConstArrayView<DisplayLine> choices,\n                           DisplayCoord anchor, Face fg, Face bg,\n                           MenuStyle style) = 0;\n    virtual void menu_select(int selected) = 0;\n    virtual void menu_hide() = 0;\n\n    virtual void info_show(const DisplayLine& title,\n                           const DisplayLineList& content,\n                           DisplayCoord anchor, Face face,\n                           InfoStyle style) = 0;\n    virtual void info_hide() = 0;\n\n    virtual void draw(const DisplayBuffer& display_buffer,\n                      DisplayCoord cursor_pos,\n                      const Face& default_face,\n                      const Face& padding_face,\n                      ColumnCount widget_columns) = 0;\n\n    virtual void draw_status(const DisplayLine& prompt,\n                             const DisplayLine& content,\n                             const ColumnCount cursor_pos,\n                             const DisplayLine& mode_line,\n                             const Face& default_face) = 0;\n\n    virtual DisplayCoord dimensions() = 0;\n\n    virtual void refresh(bool force) = 0;\n\n    virtual void set_on_key(OnKeyCallback callback) = 0;\n    virtual void set_on_paste(OnPasteCallback callback) = 0;\n\n    using Options = HashMap<String, String, MemoryDomain::Options>;\n    virtual void set_ui_options(const Options& options) = 0;\n};\n\n}\n\n#endif // user_interface_hh_INCLUDED\n"
  },
  {
    "path": "src/utf8.hh",
    "content": "#ifndef utf8_hh_INCLUDED\n#define utf8_hh_INCLUDED\n\n#include \"assert.hh\"\n#include \"unicode.hh\"\n#include \"units.hh\"\n\nnamespace Kakoune\n{\n\nnamespace utf8\n{\n\ntemplate<typename Iterator>\n[[gnu::always_inline]]\ninline char read(Iterator& it) noexcept { char c = *it; ++it; return c; }\n\n// return true if it points to the first byte of a (either single or\n// multibyte) character\n[[gnu::always_inline]]\ninline bool is_character_start(char c) noexcept\n{\n    return (c & 0xC0) != 0x80;\n}\n\nnamespace InvalidPolicy\n{\n\nstruct Assert\n{\n    Codepoint operator()(Codepoint cp) const { kak_assert(false); return cp; }\n};\n\nstruct Pass\n{\n    Codepoint operator()(Codepoint cp) const noexcept { return cp; }\n};\n\n}\n\ntemplate<typename InvalidPolicy = utf8::InvalidPolicy::Pass,\n         typename Iterator, typename Sentinel>\n[[gnu::noinline]]\nCodepoint read_codepoint_multibyte(Iterator& it, const Sentinel& end, char byte)\n    noexcept(noexcept(InvalidPolicy{}(0)))\n{\n    if (it == end)\n        return InvalidPolicy{}(byte);\n\n    if ((byte & 0xE0) == 0xC0) // 110xxxxx\n        return ((byte & 0x1F) << 6) | (read(it) & 0x3F);\n\n    if ((byte & 0xF0) == 0xE0) // 1110xxxx\n    {\n        Codepoint cp = ((byte & 0x0F) << 12) | ((read(it) & 0x3F) << 6);\n        if (it == end)\n            return InvalidPolicy{}(cp);\n        return cp | (read(it) & 0x3F);\n    }\n\n    if ((byte & 0xF8) == 0xF0) // 11110xxx\n    {\n        Codepoint cp = ((byte & 0x0F) << 18) | ((read(it) & 0x3F) << 12);\n        if (it == end)\n            return InvalidPolicy{}(cp);\n        cp |= (read(it) & 0x3F) << 6;\n        if (it == end)\n            return InvalidPolicy{}(cp);\n        return cp | (read(it) & 0x3F);\n    }\n    return InvalidPolicy{}(byte);\n}\n\n// returns the codepoint of the character whose first byte\n// is pointed by it\ntemplate<typename InvalidPolicy = utf8::InvalidPolicy::Pass,\n         typename Iterator, typename Sentinel>\nCodepoint read_codepoint(Iterator& it, const Sentinel& end)\n    noexcept(noexcept(InvalidPolicy{}(0)))\n{\n    if (it == end)\n        return InvalidPolicy{}(-1);\n    unsigned char byte = read(it);\n    if ((byte & 0x80) == 0) [[likely]] // 0xxxxxxx\n        return byte;\n    return read_codepoint_multibyte(it, end, byte);\n}\n\ntemplate<typename InvalidPolicy = utf8::InvalidPolicy::Pass,\n         typename Iterator, typename Sentinel>\nCodepoint codepoint(Iterator it, const Sentinel& end)\n    noexcept(noexcept(read_codepoint<InvalidPolicy>(it, end)))\n{\n    return read_codepoint<InvalidPolicy>(it, end);\n}\n\ntemplate<typename InvalidPolicy = utf8::InvalidPolicy::Pass>\nByteCount codepoint_size(char byte)\n    noexcept(noexcept(InvalidPolicy{}(0)))\n{\n    if ((byte & 0x80) == 0) // 0xxxxxxx\n        return 1;\n    else if ((byte & 0xE0) == 0xC0) // 110xxxxx\n        return 2;\n    else if ((byte & 0xF0) == 0xE0) // 1110xxxx\n        return 3;\n    else if ((byte & 0xF8) == 0xF0) // 11110xxx\n        return 4;\n    else\n    {\n        InvalidPolicy{}(byte);\n        return 1;\n    }\n}\n\ntemplate<typename InvalidPolicy = utf8::InvalidPolicy::Pass>\nByteCount codepoint_size(Codepoint cp)\n    noexcept(noexcept(InvalidPolicy{}(0)))\n{\n    if (cp <= 0x7F)\n        return 1;\n    else if (cp <= 0x7FF)\n        return 2;\n    else if (cp <= 0xFFFF)\n        return 3;\n    else if (cp <= 0x10FFFF)\n        return 4;\n    else\n    {\n        InvalidPolicy{}(cp);\n        return 0;\n    }\n}\n\ntemplate<typename Iterator, typename Sentinel>\nvoid to_next(Iterator& it, const Sentinel& end) noexcept\n{\n    if (it != end)\n        ++it;\n    while (it != end and not is_character_start(*it))\n        ++it;\n}\n\n// returns an iterator to next character first byte\ntemplate<typename Iterator, typename Sentinel>\nIterator next(Iterator it, const Sentinel& end) noexcept\n{\n    to_next(it, end);\n    return it;\n}\n\n// returns it's parameter if it points to a character first byte,\n// or else returns next character first byte\ntemplate<typename Iterator, typename Sentinel>\nIterator finish(Iterator it, const Sentinel& end) noexcept\n{\n    while (it != end and (*(it) & 0xC0) == 0x80)\n        ++it;\n    return it;\n}\n\ntemplate<typename Iterator, typename Sentinel>\nvoid to_previous(Iterator& it, const Sentinel& begin) noexcept\n{\n    if (it != begin)\n        --it;\n    while (it != begin and not is_character_start(*it))\n        --it;\n}\n// returns an iterator to the previous character first byte\ntemplate<typename Iterator, typename Sentinel>\nIterator previous(Iterator it, const Sentinel& begin) noexcept\n{\n    to_previous(it, begin);\n    return it;\n}\n\n// returns an iterator pointing to the first byte of the\n// dth character after (or before if d < 0) the character\n// pointed by it\ntemplate<typename Iterator, typename Sentinel>\nIterator advance(Iterator it, const Sentinel& end, CharCount d) noexcept\n{\n    if (it == end)\n        return it;\n\n    if (d < 0)\n    {\n        while (it != end and d++ != 0)\n            to_previous(it, end);\n    }\n    else if (d > 0)\n    {\n        while (it != end and d-- != 0)\n            to_next(it, end);\n    }\n    return it;\n}\n\n// returns an iterator pointing to the first byte of the\n// character at the dth column after (or before if d < 0)\n// the character pointed by it\ntemplate<typename Iterator, typename Sentinel>\nIterator advance(Iterator it, const Sentinel& end, ColumnCount d) noexcept\n{\n    if (it == end)\n        return it;\n\n    if (d < 0)\n    {\n        while (it != end and d < 0)\n        {\n            auto cur = it;\n            to_previous(it, end);\n            d += codepoint_width(codepoint(it, cur));\n        }\n    }\n    else if (d > 0)\n    {\n        auto begin = it;\n        while (it != end and d > 0)\n        {\n            d -= codepoint_width(read_codepoint(it, end));\n            if (it != end and d < 0)\n                to_previous(it, begin);\n        }\n    }\n    return it;\n}\n\n// returns the character count between begin and end\ntemplate<typename Iterator, typename Sentinel>\nCharCount distance(Iterator begin, const Sentinel& end) noexcept\n{\n    CharCount dist = 0;\n\n    while (begin != end)\n    {\n        if (is_character_start(read(begin)))\n            ++dist;\n    }\n    return dist;\n}\n\n// returns the column count between begin and end\ntemplate<typename Iterator, typename Sentinel>\nColumnCount column_distance(Iterator begin, const Sentinel& end) noexcept\n{\n    ColumnCount dist = 0;\n\n    while (begin != end)\n        dist += codepoint_width(read_codepoint(begin, end));\n    return dist;\n}\n\n// returns an iterator to the first byte of the character it is into\ntemplate<typename Iterator, typename Sentinel>\nIterator character_start(Iterator it, const Sentinel& begin) noexcept\n{\n    while (it != begin and not is_character_start(*it))\n        --it;\n    return it;\n}\n\ntemplate<typename InvalidPolicy = utf8::InvalidPolicy::Pass,\n         typename Iterator, typename Sentinel>\nCodepoint prev_codepoint(Iterator it, const Sentinel& begin) noexcept\n{\n    if (it <= begin)\n        return InvalidPolicy{}(-1);\n    return codepoint<InvalidPolicy>(character_start(it -1, begin), it);\n}\n\n\ntemplate<typename OutputIterator, typename InvalidPolicy = utf8::InvalidPolicy::Pass>\nvoid dump(OutputIterator&& it, Codepoint cp)\n{\n    if (cp <= 0x7F)\n        *it++ = cp;\n    else if (cp <= 0x7FF)\n    {\n        *it++ = 0xC0 | (cp >> 6);\n        *it++ = 0x80 | (cp & 0x3F);\n    }\n    else if (cp <= 0xFFFF)\n    {\n        *it++ = 0xE0 | (cp >> 12);\n        *it++ = 0x80 | ((cp >> 6) & 0x3F);\n        *it++ = 0x80 | (cp & 0x3F);\n    }\n    else if (cp <= 0x10FFFF)\n    {\n        *it++ = 0xF0 | (cp >> 18);\n        *it++ = 0x80 | ((cp >> 12) & 0x3F);\n        *it++ = 0x80 | ((cp >> 6)  & 0x3F);\n        *it++ = 0x80 | (cp & 0x3F);\n    }\n    else\n        InvalidPolicy{}(cp);\n}\n\n}\n\n}\n\n#endif // utf8_hh_INCLUDED\n"
  },
  {
    "path": "src/utf8_iterator.hh",
    "content": "#ifndef utf8_iterator_hh_INCLUDED\n#define utf8_iterator_hh_INCLUDED\n\n#include \"utf8.hh\"\n\n#include <iterator>\n\nnamespace Kakoune\n{\n\nnamespace utf8\n{\n\n// adapter for an iterator on bytes which permits to iterate\n// on unicode codepoints instead.\ntemplate<typename BaseIt,\n         typename Sentinel = BaseIt,\n         typename CodepointType = Codepoint,\n         typename DifferenceType = CharCount,\n         typename InvalidPolicy = utf8::InvalidPolicy::Pass>\nclass iterator\n{\npublic:\n    using value_type = CodepointType;\n    using difference_type = DifferenceType;\n    using pointer = CodepointType*;\n    using reference = CodepointType&;\n    using iterator_category = std::bidirectional_iterator_tag;\n\n    iterator() = default;\n    constexpr static bool noexcept_policy = noexcept(InvalidPolicy{}(0));\n\n    iterator(BaseIt it, Sentinel begin, Sentinel end) noexcept\n        : m_it{std::move(it)}, m_begin{std::move(begin)}, m_end{std::move(end)}\n    {}\n\n    template<typename Container>\n    iterator(BaseIt it, const Container& c) noexcept\n        : m_it{std::move(it)}, m_begin{std::begin(c)}, m_end{std::end(c)}\n    {}\n\n    iterator& operator++() noexcept\n    {\n        utf8::to_next(m_it, m_end);\n        return *this;\n    }\n\n    iterator operator++(int) noexcept\n    {\n        iterator save = *this;\n        ++*this;\n        return save;\n    }\n\n    iterator& operator--() noexcept\n    {\n        utf8::to_previous(m_it, m_begin);\n        return *this;\n    }\n\n    iterator operator--(int) noexcept\n    {\n        iterator save = *this;\n        --*this;\n        return save;\n    }\n\n    iterator operator+(DifferenceType count) const noexcept\n    {\n        iterator res = *this;\n        res += count;\n        return res;\n    }\n\n    iterator& operator+=(DifferenceType count) noexcept\n    {\n        if (count < 0)\n            return operator-=(-count);\n\n        while (count--)\n            operator++();\n        return *this;\n    }\n\n    iterator operator-(DifferenceType count) const noexcept\n    {\n        iterator res = *this;\n        res -= count;\n        return res;\n    }\n\n    iterator& operator-=(DifferenceType count) noexcept\n    {\n        if (count < 0)\n            return operator+=(-count);\n\n        while (count--)\n            operator--();\n        return *this;\n    }\n\n    bool operator==(const iterator& other) const noexcept { return m_it == other.m_it; }\n    auto operator<=>(const iterator& other) const noexcept { return m_it <=> other.m_it; }\n\n    template<typename T>\n        requires std::is_same_v<T, BaseIt> or std::is_same_v<T, Sentinel>\n    bool operator==(const T& other) const noexcept { return m_it == other; }\n    auto operator<=>(const BaseIt& other) const noexcept { return m_it <=> other; }\n\n    DifferenceType operator-(const iterator& other) const noexcept(noexcept_policy)\n    {\n        return (DifferenceType)utf8::distance<InvalidPolicy>(other.m_it, m_it);\n    }\n\n    CodepointType operator*() const noexcept(noexcept_policy)\n    {\n        return (CodepointType)utf8::codepoint<InvalidPolicy>(m_it, m_end);\n    }\n\n    CodepointType read() noexcept(noexcept_policy)\n    {\n        return (CodepointType)utf8::read_codepoint<InvalidPolicy>(m_it, m_end);\n    }\n\n    const BaseIt& base() const noexcept { return m_it; }\n\nprivate:\n    BaseIt m_it;\n    Sentinel m_begin;\n    Sentinel m_end;\n};\n\n}\n\n}\n#endif // utf8_iterator_hh_INCLUDED\n"
  },
  {
    "path": "src/utils.hh",
    "content": "#ifndef utils_hh_INCLUDED\n#define utils_hh_INCLUDED\n\n#include \"assert.hh\"\n\n#include <utility>\n#include <type_traits>\n\nnamespace Kakoune\n{\n\n// *** Singleton ***\n//\n// Singleton helper class, every singleton type T should inherit\n// from Singleton<T> to provide a consistent interface.\ntemplate<typename T>\nclass Singleton\n{\npublic:\n    Singleton(const Singleton&) = delete;\n    Singleton& operator=(const Singleton&) = delete;\n\n    static T& instance()\n    {\n        kak_assert(ms_instance);\n        return *static_cast<T*>(ms_instance);\n    }\n\n    static bool has_instance()\n    {\n        return ms_instance != nullptr;\n    }\n\nprotected:\n    Singleton()\n    {\n        kak_assert(ms_instance == nullptr);\n        ms_instance = this;\n    }\n\n    ~Singleton()\n    {\n        kak_assert(ms_instance == this);\n        ms_instance = nullptr;\n    }\n\nprivate:\n    static Singleton* ms_instance;\n};\n\ntemplate<typename T>\nSingleton<T>* Singleton<T>::ms_instance = nullptr;\n\n// *** On scope end ***\n//\n// OnScopeEnd provides a way to register some code to be\n// executed when current scope closes.\n//\n// usage:\n// auto cleaner = OnScopeEnd([]() { ... });\n//\n// This permits to cleanup c-style resources without implementing\n// a wrapping class\ntemplate<typename T>\nclass [[nodiscard]] OnScopeEnd\n{\npublic:\n    [[gnu::always_inline]]\n    OnScopeEnd(T func) : m_valid{true}, m_func{std::move(func)} {}\n\n    [[gnu::always_inline]]\n    OnScopeEnd(OnScopeEnd&& other)\n      : m_valid{other.m_valid}, m_func{std::move(other.m_func)}\n    { other.m_valid = false; }\n\n    [[gnu::always_inline]]\n    void trigger() { if (m_valid) m_func(); m_valid = false; }\n\n    [[gnu::always_inline]]\n    ~OnScopeEnd() noexcept(noexcept(m_func())) { if (m_valid) m_func(); }\n\nprivate:\n    bool m_valid;\n    T m_func;\n};\n\n// bool that can be set (to true) multiple times, and will\n// be false only when unset the same time;\nstruct NestedBool\n{\n    void set() { m_count++; }\n    void unset() { kak_assert(m_count > 0); m_count--; }\n\n    operator bool() const { return m_count > 0; }\nprivate:\n    int m_count = 0;\n};\n\nstruct ScopedSetBool\n{\n    ScopedSetBool(NestedBool& nested_bool, bool condition = true)\n        : m_nested_bool(nested_bool), m_condition(condition)\n    {\n        if (m_condition)\n            m_nested_bool.set();\n    }\n\n    ScopedSetBool(ScopedSetBool&& other)\n        : m_nested_bool(other.m_nested_bool), m_condition(other.m_condition)\n    {\n        other.m_condition = false;\n    }\n\n    ~ScopedSetBool()\n    {\n        if (m_condition)\n            m_nested_bool.unset();\n    }\n\nprivate:\n    NestedBool& m_nested_bool;\n    bool m_condition;\n};\n\ntemplate<typename T>\nconst T& clamp(const T& val, const T& min, const T& max)\n{\n    return (val < min ? min : (val > max ? max : val));\n}\n\ntemplate<typename Iterator, typename EndIterator, typename T>\nbool skip_while(Iterator& it, const EndIterator& end, T condition)\n{\n    while (it != end and condition(*it))\n        ++it;\n    return it != end;\n}\n\ntemplate<typename Iterator, typename BeginIterator, typename T>\nbool skip_while_reverse(Iterator& it, const BeginIterator& begin, T condition)\n{\n    while (it != begin and condition(*it))\n        --it;\n    return condition(*it);\n}\n\ntemplate<typename E>\nauto to_underlying(E value)\n{\n    return static_cast<std::underlying_type_t<E>>(value);\n}\n\ntemplate<typename> class FunctionRef;\n\ntemplate<typename From, typename To>\nconcept ConvertibleTo = std::is_convertible_v<From, To>;\n\ntemplate<typename Res, typename... Args>\nclass FunctionRef<Res(Args...)>\n{\npublic:\n    FunctionRef()\n      : m_target{nullptr},\n        m_invoker{[](void* target, Args... args) {\n            if constexpr (!std::is_same_v<Res, void>) return Res{};\n        }}\n    {}\n\n    template<typename Target>\n        requires requires (Target t, Args... a) {\n            requires not std::is_same_v<FunctionRef, std::remove_cvref_t<Target>>;\n            { t(a...) } -> ConvertibleTo<Res>;\n        }\n    FunctionRef(Target&& target)\n      : m_target{&target},\n        m_invoker{[](void* target, Args... args) {\n            return (*reinterpret_cast<Target*>(target))(static_cast<Args>(args)...);\n        }}\n    {}\n\n    Res operator()(Args... args) const\n    {\n        return m_invoker(m_target, static_cast<Args>(args)...);\n    }\n\nprivate:\n    using Invoker = Res (void*, Args...);\n    void* m_target;\n    Invoker* m_invoker;\n};\n\ntemplate<typename... Funcs>\nstruct Overload : Funcs...\n{\n    using Funcs::operator()...;\n};\n\ntemplate<typename... Funcs>\nauto overload(Funcs&&... funcs)\n{\n    return Overload<std::remove_cvref_t<Funcs>...>{std::forward<Funcs>(funcs)...};\n}\n\n}\n\n#endif // utils_hh_INCLUDED\n"
  },
  {
    "path": "src/value.hh",
    "content": "#ifndef value_hh_INCLUDED\n#define value_hh_INCLUDED\n\n#include \"hash_map.hh\"\n#include \"meta.hh\"\n#include \"unique_ptr.hh\"\n\n#include <type_traits>\n#include <typeinfo>\n\nnamespace Kakoune\n{\n\nstruct bad_value_cast {};\n\nstruct Value\n{\n    Value() = default;\n\n    template<typename T> requires (not std::is_same_v<Value, T>)\n    Value(T&& val)\n        : m_value{new Model<std::remove_cvref_t<T>>{std::forward<T>(val)}} {}\n\n    template<typename T>\n    Value(Meta::Type<T>, auto&&... args) :\n        m_value(new Model<T>(std::forward<decltype(args)>(args)...)) {}\n\n    Value(const Value& val) = delete;\n    Value(Value&&) = default;\n\n    Value& operator=(const Value& val) = delete;\n    Value& operator=(Value&& val) = default;\n\n    explicit operator bool() const { return (bool)m_value; }\n\n    template<typename T>\n    bool is_a() const\n    {\n        return m_value and m_value->type() == typeid(T);\n    }\n\n    template<typename T>\n    T& as()\n    {\n        if (not is_a<T>())\n            throw bad_value_cast{};\n        return static_cast<Model<T>*>(m_value.get())->m_content;\n    }\n\n    template<typename T>\n    const T& as() const\n    {\n        return const_cast<Value*>(this)->as<T>();\n    }\n\nprivate:\n    struct Concept\n    {\n        virtual ~Concept() = default;\n        virtual const std::type_info& type() const = 0;\n    };\n\n    template<typename T>\n    struct Model : public Concept, public UseMemoryDomain<MemoryDomain::Values>\n    {\n        Model(auto&&... args) : m_content(std::forward<decltype(args)>(args)...) {}\n        const std::type_info& type() const override { return typeid(T); }\n\n        T m_content;\n    };\n\n    UniquePtr<Concept> m_value;\n};\n\nenum class ValueId : int {};\n\ninline ValueId get_free_value_id()\n{\n    static int next = 0;\n    return (ValueId)(next++);\n}\n\nusing ValueMap = HashMap<ValueId, Value, MemoryDomain::Values>;\n\n}\n\n#endif // value_hh_INCLUDED\n"
  },
  {
    "path": "src/vector.hh",
    "content": "#ifndef vector_hh_INCLUDED\n#define vector_hh_INCLUDED\n\n#include \"memory.hh\"\n#include \"hash.hh\"\n\n#include <vector>\n\nnamespace Kakoune\n{\n\ntemplate<typename T, MemoryDomain domain = memory_domain(Meta::Type<T>{})>\nusing Vector = std::vector<T, Allocator<T, domain>>;\n\ntemplate<typename T, MemoryDomain domain>\nsize_t hash_value(const Vector<T, domain>& vector)\n{\n    size_t hash = 0x1235678;\n    for (auto&& elem : vector)\n        hash = combine_hash(hash, hash_value(elem));\n    return hash;\n}\n\n}\n\n#endif // vector_hh_INCLUDED\n"
  },
  {
    "path": "src/window.cc",
    "content": "#include \"window.hh\"\n\n#include \"assert.hh\"\n#include \"buffer.hh\"\n#include \"buffer_utils.hh\"\n#include \"context.hh\"\n#include \"client.hh\"\n#include \"highlighter.hh\"\n#include \"hook_manager.hh\"\n#include \"input_handler.hh\"\n#include \"face_registry.hh\"\n#include \"option_manager.hh\"\n#include \"client.hh\"\n#include \"debug.hh\"\n#include \"option.hh\"\n#include \"option_types.hh\"\n#include \"profile.hh\"\n\n#include <algorithm>\n\nnamespace Kakoune\n{\n\n// Implementation in highlighters.cc\nvoid setup_builtin_highlighters(HighlighterGroup& group);\n\nWindow::Window(Buffer& buffer)\n    : Scope(buffer),\n      m_buffer(&buffer),\n      m_builtin_highlighters{highlighters()}\n{\n    run_hook_in_own_context(Hook::WinCreate, buffer.name());\n\n    options().register_watcher(*this);\n\n    setup_builtin_highlighters(m_builtin_highlighters.group());\n\n    // gather as on_option_changed can mutate the option managers\n    for (auto& option : options().flatten_options()\n                      | transform([](auto& ptr) { return ptr.get(); })\n                      | gather<Vector<const Option*>>())\n        on_option_changed(*option);\n}\n\nWindow::~Window()\n{\n    options().unregister_watcher(*this);\n}\n\nvoid Window::set_client(Client* client)\n{\n    m_client = client;\n}\n\nvoid Window::scroll(LineCount offset)\n{\n    m_position.line = std::max(0_line, m_position.line + offset);\n}\n\nvoid Window::display_line_at(LineCount buffer_line, LineCount display_line)\n{\n    if (display_line >= 0 or display_line < m_dimensions.line)\n        m_position.line = std::max(0_line, buffer_line - display_line);\n}\n\nvoid Window::center_line(LineCount buffer_line)\n{\n    display_line_at(buffer_line, m_dimensions.line/2_line);\n}\n\nvoid Window::scroll(ColumnCount offset)\n{\n    m_position.column = std::max(0_col, m_position.column + offset);\n}\n\nvoid Window::display_column_at(ColumnCount buffer_column, ColumnCount display_column)\n{\n    if (display_column >= 0 or display_column < m_dimensions.column)\n        m_position.column = std::max(0_col, buffer_column - display_column);\n}\n\nvoid Window::center_column(ColumnCount buffer_column)\n{\n    display_column_at(buffer_column, m_dimensions.column/2_col);\n}\n\nstatic uint32_t compute_faces_hash(const FaceRegistry& faces)\n{\n    uint32_t hash = 0;\n    for (auto&& face : faces.flatten_faces() | transform(&FaceRegistry::FaceMap::Item::value))\n        hash = combine_hash(hash, face.base.empty() ? hash_value(face.face) : hash_value(face.base));\n    return hash;\n}\n\nWindow::Setup Window::build_setup(const Context& context) const\n{\n    return {m_position, m_dimensions,\n            context.buffer().timestamp(),\n            compute_faces_hash(context.faces()),\n            context.selections().main_index(),\n            context.selections() | gather<Vector<BasicSelection, MemoryDomain::Display>>()};\n}\n\nbool Window::needs_redraw(const Context& context) const\n{\n    auto& selections = context.selections();\n    return m_position != m_last_setup.position or\n        m_dimensions != m_last_setup.dimensions or\n        context.buffer().timestamp() != m_last_setup.timestamp or\n        selections.main_index() != m_last_setup.main_selection or\n        selections.size() != m_last_setup.selections.size() or\n        compute_faces_hash(context.faces()) != m_last_setup.faces_hash or\n        not std::equal(selections.begin(), selections.end(),\n                       m_last_setup.selections.begin(), m_last_setup.selections.end());\n}\n\nconst DisplayBuffer& Window::update_display_buffer(const Context& context)\n{\n    ProfileScope profile{context.options()[\"debug\"].get<DebugFlags>(), [&](std::chrono::microseconds duration) {\n        write_to_debug_buffer(format(\"window display update for '{}' took {} us\",\n                                     buffer().display_name(), (size_t)duration.count()));\n    }, not (buffer().flags() & Buffer::Flags::Debug)};\n\n    if (m_display_buffer.timestamp() != -1)\n    {\n        for (auto&& change : buffer().changes_since(m_display_buffer.timestamp()))\n        {\n            if (change.type == Buffer::Change::Insert and change.begin.line < m_position.line)\n                m_position.line += change.end.line - change.begin.line;\n            if (change.type == Buffer::Change::Erase and change.begin.line < m_position.line)\n                m_position.line = std::max(m_position.line - (change.end.line - change.begin.line), change.begin.line);\n        }\n    }\n\n    DisplayLineList& lines = m_display_buffer.lines();\n    m_display_buffer.set_timestamp(buffer().timestamp());\n    lines.clear();\n\n    if (m_dimensions.line == 0 or m_dimensions.column == 0)\n        return m_display_buffer;\n\n    kak_assert(&buffer() == &context.buffer());\n    DisplaySetup setup = compute_display_setup(context);\n\n    if (setup.line_count != m_last_display_setup.line_count or\n        setup.widget_columns != m_last_display_setup.widget_columns)\n    {\n        // Technically the window has not resized, but most things that hook WinResize probably want to be notfied anyway\n        m_resize_hook_pending = true;\n    }\n\n    for (LineCount line = 0; line < setup.line_count; ++line)\n    {\n        LineCount buffer_line = setup.first_line + line;\n        if (buffer_line >= buffer().line_count())\n            break;\n        lines.emplace_back(AtomList{{buffer(), {buffer_line, {buffer_line, buffer()[buffer_line].length()}}}});\n    }\n\n    m_display_buffer.compute_range();\n    const BufferRange range{{0,0}, buffer().end_coord()};\n    for (auto pass : {HighlightPass::Replace, HighlightPass::Wrap, HighlightPass::Move})\n        m_builtin_highlighters.highlight({context, setup, pass, {}}, m_display_buffer, range);\n\n    if (context.ensure_cursor_visible)\n    {\n        auto cursor_pos = display_coord(context.selections().main().cursor());\n        kak_assert(cursor_pos);\n\n        if (auto line_overflow = cursor_pos->line - m_dimensions.line + setup.scroll_offset.line + 1; line_overflow > 0)\n        {\n            lines.erase(lines.begin(), lines.begin() + (size_t)line_overflow);\n            setup.first_line = lines.begin()->range().begin.line;\n        }\n\n        auto max_first_column = cursor_pos->column - (setup.widget_columns + setup.scroll_offset.column);\n        setup.first_column = std::max(0_col, std::min(setup.first_column, max_first_column));\n\n        auto min_first_column = cursor_pos->column - (m_dimensions.column - setup.scroll_offset.column) + 1;\n        setup.first_column = std::max(setup.first_column, min_first_column);\n    }\n\n    for (auto& line : m_display_buffer.lines())\n        line.trim_from(setup.widget_columns, setup.first_column, m_dimensions.column);\n    if (m_display_buffer.lines().size() > m_dimensions.line)\n        m_display_buffer.lines().resize((size_t)m_dimensions.line);\n\n    m_builtin_highlighters.highlight({context, setup, HighlightPass::Colorize, {}}, m_display_buffer, range);\n\n    m_display_buffer.optimize();\n\n    set_position({setup.first_line, setup.first_column});\n    m_last_setup = build_setup(context);\n    m_last_display_setup = setup;\n\n    return m_display_buffer;\n}\n\nvoid Window::set_position(DisplayCoord position)\n{\n    m_position.line = clamp(position.line, 0_line, buffer().line_count()-1);\n    m_position.column = std::max(0_col, position.column);\n}\n\nvoid Window::set_dimensions(DisplayCoord dimensions)\n{\n    if (m_dimensions != dimensions)\n    {\n        m_dimensions = dimensions;\n        m_resize_hook_pending = true;\n    }\n}\n\nvoid Window::run_resize_hook_ifn()\n{\n    if (m_resize_hook_pending)\n    {\n        m_resize_hook_pending = false;\n        run_hook_in_own_context(Hook::WinResize,\n                                format(\"{}.{}\", m_dimensions.line, m_dimensions.column));\n    }\n}\n\nstatic void check_display_setup(const DisplaySetup& setup, const Window& window)\n{\n    kak_assert(setup.first_line >= 0 and setup.first_line < window.buffer().line_count());\n    kak_assert(setup.first_column >= 0);\n    kak_assert(setup.line_count >= 0);\n}\n\nDisplaySetup Window::compute_display_setup(const Context& context) const\n{\n    DisplayCoord offset = options()[\"scrolloff\"].get<DisplayCoord>();\n    offset.line = std::min(offset.line, (m_dimensions.line + 1) / 2);\n    offset.column = std::min(offset.column, (m_dimensions.column + 1) / 2);\n    const auto& cursor = context.selections().main().cursor();\n    DisplaySetup setup{m_position.line, m_dimensions.line, m_position.column, 0_col, offset};\n    if (context.ensure_cursor_visible and\n        cursor.line - offset.line < setup.first_line)\n        setup.first_line = clamp(cursor.line - offset.line, 0_line, buffer().line_count()-1);\n\n    for (auto pass : {HighlightPass::Move, HighlightPass::Wrap, HighlightPass::Replace})\n        m_builtin_highlighters.compute_display_setup({context, setup, pass, {}}, setup);\n\n    if (context.ensure_cursor_visible and\n        cursor.line + offset.line >= setup.first_line + setup.line_count)\n        setup.first_line = std::min(cursor.line + offset.line - setup.line_count + 1, buffer().line_count()-1);\n\n    setup.first_line = std::min(setup.first_line, buffer().line_count()-1);\n    check_display_setup(setup, *this);\n\n    return setup;\n}\n\nnamespace\n{\nColumnCount find_display_column(const DisplayLine& line, const Buffer& buffer,\n                                BufferCoord coord)\n{\n    ColumnCount column = 0;\n    for (auto& atom : line)\n    {\n        if (atom.has_buffer_range() and\n            coord >= atom.begin() and coord < atom.end())\n        {\n            if (atom.type() == DisplayAtom::Range)\n                column += utf8::column_distance(get_iterator(buffer, atom.begin()),\n                                                get_iterator(buffer, coord));\n            return column;\n        }\n        column += atom.length();\n    }\n    return column;\n}\n\nBufferCoord find_buffer_coord(const DisplayLine& line, const Buffer& buffer,\n                              ColumnCount column)\n{\n    const auto& range = line.range();\n    for (auto& atom : line)\n    {\n        ColumnCount len = atom.length();\n        if (atom.has_buffer_range() and column < len)\n        {\n            if (atom.type() == DisplayAtom::Range)\n                return buffer.clamp(\n                    utf8::advance(get_iterator(buffer, atom.begin()),\n                                  get_iterator(buffer, range.end),\n                                  std::max(0_col, column)).coord());\n             return buffer.clamp(atom.begin());\n        }\n        column -= len;\n    }\n    return range.end == BufferCoord{0,0} ?\n        range.end : buffer.prev(range.end);\n}\n}\n\nOptional<DisplayCoord> Window::display_coord(BufferCoord coord) const\n{\n    if (m_display_buffer.timestamp() != buffer().timestamp())\n        return {};\n\n    LineCount l = 0;\n    for (auto& line : m_display_buffer.lines())\n    {\n        auto& range = line.range();\n        if (range.begin <= coord and coord < range.end)\n            return DisplayCoord{l, find_display_column(line, buffer(), coord)};\n        ++l;\n    }\n    return {};\n}\n\nOptional<BufferCoord> Window::buffer_coord(DisplayCoord coord) const\n{\n    if (m_display_buffer.timestamp() != buffer().timestamp() or\n        m_display_buffer.lines().empty())\n        return {};\n    if (coord <= 0_line)\n        return {};\n    if ((size_t)coord.line >= m_display_buffer.lines().size())\n        return {};\n\n    return find_buffer_coord(m_display_buffer.lines()[(int)coord.line],\n                             buffer(), coord.column);\n}\n\nvoid Window::clear_display_buffer()\n{\n    m_display_buffer = DisplayBuffer{};\n}\n\nvoid Window::on_option_changed(const Option& option)\n{\n    run_hook_in_own_context(Hook::WinSetOption, format(\"{}={}\", option.name(), option.get_desc_string()));\n}\n\n\nvoid Window::run_hook_in_own_context(Hook hook, StringView param, String client_name)\n{\n    if (m_buffer->flags() & Buffer::Flags::NoHooks)\n        return;\n\n    InputHandler hook_handler{{ *m_buffer, Selection{} },\n                              Context::Flags::Draft,\n                              std::move(client_name)};\n    hook_handler.context().set_window(*this);\n    if (m_client)\n        hook_handler.context().set_client(*m_client);\n\n    hooks().run_hook(hook, param, hook_handler.context());\n}\n}\n"
  },
  {
    "path": "src/window.hh",
    "content": "#ifndef window_hh_INCLUDED\n#define window_hh_INCLUDED\n\n#include \"display_buffer.hh\"\n#include \"highlighter_group.hh\"\n#include \"option.hh\"\n#include \"optional.hh\"\n#include \"safe_ptr.hh\"\n#include \"selection.hh\"\n#include \"scope.hh\"\n\nnamespace Kakoune\n{\n\nenum class Hook;\nclass Client;\n\n// A Window is a view onto a Buffer\nclass Window final : public SafeCountable, public Scope, private OptionWatcher\n{\npublic:\n    Window(Buffer& buffer);\n    ~Window();\n\n    const DisplayCoord& position() const { return m_position; }\n    void set_position(DisplayCoord position);\n\n    const DisplayCoord& dimensions() const { return m_dimensions; }\n    void set_dimensions(DisplayCoord dimensions);\n\n    void scroll(LineCount offset);\n    void center_line(LineCount buffer_line);\n    void display_line_at(LineCount buffer_line, LineCount display_line);\n\n    void scroll(ColumnCount offset);\n    void center_column(ColumnCount buffer_column);\n    void display_column_at(ColumnCount buffer_column, ColumnCount display_column);\n\n    const DisplayBuffer& update_display_buffer(const Context& context);\n\n    Optional<DisplayCoord> display_coord(BufferCoord coord) const;\n    Optional<BufferCoord> buffer_coord(DisplayCoord coord) const;\n\n    Buffer& buffer() const { return *m_buffer; }\n\n    bool needs_redraw(const Context& context) const;\n\n    void set_client(Client* client);\n\n    void clear_display_buffer();\n    void run_resize_hook_ifn();\n\n    const DisplaySetup& last_display_setup() const { return m_last_display_setup; }\n\nprivate:\n    Window(const Window&) = delete;\n\n    DisplaySetup compute_display_setup(const Context& context) const;\n    void on_option_changed(const Option& option) override;\n\n    friend class ClientManager;\n    void run_hook_in_own_context(Hook hook, StringView param,\n                                 String client_name = \"\");\n\n    SafePtr<Buffer> m_buffer;\n    SafePtr<Client> m_client;\n\n    DisplayCoord m_position;\n    DisplayCoord m_dimensions;\n    DisplayBuffer m_display_buffer;\n\n    Highlighters m_builtin_highlighters;\n    bool m_resize_hook_pending = false;\n    DisplaySetup m_last_display_setup;\n\n    struct Setup\n    {\n        DisplayCoord position;\n        DisplayCoord dimensions;\n        size_t timestamp;\n        size_t faces_hash;\n        size_t main_selection;\n        Vector<BasicSelection, MemoryDomain::Display> selections;\n    };\n    Setup build_setup(const Context& context) const;\n    Setup m_last_setup;\n};\n\n}\n\n#endif // window_hh_INCLUDED\n"
  },
  {
    "path": "src/word_db.cc",
    "content": "#include \"word_db.hh\"\n\n#include \"buffer.hh\"\n#include \"line_modification.hh\"\n#include \"unit_tests.hh\"\n#include \"value.hh\"\n#include \"word_splitter.hh\"\n#include \"option_manager.hh\"\n\nnamespace Kakoune\n{\n\nWordDB& get_word_db(const Buffer& buffer)\n{\n    static const ValueId word_db_id = get_free_value_id();\n    Value& cache_val = buffer.values()[word_db_id];\n    if (not cache_val)\n        cache_val = Value(WordDB{buffer});\n    return cache_val.as<WordDB>();\n}\n\nstatic ConstArrayView<Codepoint> get_extra_word_chars(const Buffer& buffer)\n{\n    return buffer.options()[\"extra_word_chars\"].get<Vector<Codepoint, MemoryDomain::Options>>();\n}\n\nvoid WordDB::add_words(StringView line, ConstArrayView<Codepoint> extra_word_chars)\n{\n    for (auto&& w : WordSplitter{line, extra_word_chars})\n    {\n        auto hash = hash_value(w);\n        auto index = m_words.find_index(w, hash);\n        if (index >= 0)\n            ++m_words.item(index).value.refcount;\n        else\n        {\n            auto word = intern(w, hash);\n            auto view = word->strview();\n            m_words.insert({view, {std::move(word), used_letters(view), 1}}, hash);\n        }\n    }\n}\n\nvoid WordDB::remove_words(StringView line, ConstArrayView<Codepoint> extra_word_chars)\n{\n    for (auto&& w : WordSplitter{line, extra_word_chars})\n    {\n        auto it = m_words.find(w);\n        kak_assert(it != m_words.end() and it->value.refcount > 0);\n        if (--it->value.refcount == 0)\n            m_words.unordered_remove(it->key);\n    }\n}\n\nWordDB::WordDB(const Buffer& buffer)\n    : m_buffer{&buffer}\n{\n    buffer.options().register_watcher(*this);\n    rebuild_db();\n}\n\nWordDB::WordDB(WordDB&& other) noexcept\n    : m_buffer{std::move(other.m_buffer)},\n      m_timestamp{other.m_timestamp},\n      m_words{std::move(other.m_words)},\n      m_lines{std::move(other.m_lines)}\n{\n    kak_assert(m_buffer);\n    m_buffer->options().unregister_watcher(other);\n    other.m_buffer = nullptr;\n\n    m_buffer->options().register_watcher(*this);\n}\n\nWordDB::~WordDB()\n{\n    if (m_buffer)\n        m_buffer->options().unregister_watcher(*this);\n}\n\nvoid WordDB::rebuild_db()\n{\n    auto& buffer = *m_buffer;\n\n    m_words.clear();\n    m_lines.clear();\n    m_lines.reserve((int)buffer.line_count());\n    auto extra_word_chars = get_extra_word_chars(buffer);\n    for (auto line = 0_line, end = buffer.line_count(); line < end; ++line)\n    {\n        m_lines.push_back(buffer.line_storage(line));\n        add_words(m_lines.back()->strview(), extra_word_chars);\n    }\n    m_timestamp = buffer.timestamp();\n}\n\nvoid WordDB::update_db()\n{\n    auto& buffer = *m_buffer;\n\n    auto modifs = compute_line_modifications(buffer, m_timestamp);\n    m_timestamp = buffer.timestamp();\n\n    if (modifs.empty())\n        return;\n\n    Lines new_lines;\n    new_lines.reserve((int)buffer.line_count());\n\n    auto extra_word_chars = get_extra_word_chars(buffer);\n    auto old_line = 0_line;\n    for (auto& modif : modifs)\n    {\n        kak_assert(0_line <= modif.new_line and modif.new_line <= buffer.line_count());\n        kak_assert(modif.new_line < buffer.line_count() or modif.num_added == 0);\n        kak_assert(old_line <= modif.old_line);\n        while (old_line < modif.old_line)\n            new_lines.push_back(std::move(m_lines[(int)old_line++]));\n\n        kak_assert((int)new_lines.size() == (int)modif.new_line);\n\n        while (old_line < modif.old_line + modif.num_removed)\n        {\n            kak_assert(old_line < m_lines.size());\n            remove_words(m_lines[(int)old_line++]->strview(), extra_word_chars);\n        }\n\n        for (auto l = 0_line; l < modif.num_added; ++l)\n        {\n            new_lines.push_back(buffer.line_storage(modif.new_line + l));\n            add_words(new_lines.back()->strview(), extra_word_chars);\n        }\n    }\n    while (old_line != (int)m_lines.size())\n        new_lines.push_back(std::move(m_lines[(int)old_line++]));\n\n    m_lines = std::move(new_lines);\n}\n\nvoid WordDB::on_option_changed(const Option& option)\n{\n    if (option.name() == \"extra_word_chars\")\n        rebuild_db();\n}\n\nint WordDB::get_word_occurences(StringView word) const\n{\n    auto it = m_words.find(word);\n    if (it != m_words.end())\n        return it->value.refcount;\n    return 0;\n}\n\nRankedMatchList WordDB::find_matching(StringView query)\n{\n    update_db();\n    const UsedLetters letters = used_letters(query);\n    RankedMatchList res;\n    for (auto&& word : m_words)\n    {\n        if (RankedMatch match{word.key, word.value.letters, query, letters})\n            res.push_back(match);\n    }\n\n    return res;\n}\n\nUnitTest test_word_db{[]()\n{\n    auto cmp_words = [](const RankedMatch& lhs, const RankedMatch& rhs) {\n        return lhs.candidate() < rhs.candidate();\n    };\n\n    using WordList = Vector<StringView>;\n    auto eq = [](ArrayView<const RankedMatch> lhs, const WordList& rhs) {\n        return lhs.size() == rhs.size() and\n            std::equal(lhs.begin(), lhs.end(), rhs.begin(),\n                       [](const RankedMatch& lhs, const StringView& rhs) {\n                           return lhs.candidate() == rhs;\n                       });\n    };\n\n    auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create(lines)...}; };\n\n    Buffer buffer(\"test\", Buffer::Flags::None,\n                  make_lines(\"tchou mutch\\n\", \"tchou kanaky tchou\\n\", \"\\n\", \"tchaa tchaa\\n\", \"allo\\n\"));\n    WordDB word_db(buffer);\n    auto res = word_db.find_matching(\"\");\n    std::sort(res.begin(), res.end(), cmp_words);\n    kak_assert(eq(res, WordList{ \"allo\", \"kanaky\", \"mutch\", \"tchaa\", \"tchou\" }));\n    kak_assert(word_db.get_word_occurences(\"tchou\") == 3);\n    kak_assert(word_db.get_word_occurences(\"allo\") == 1);\n    buffer.erase({1, 6}, {4, 0});\n    res = word_db.find_matching(\"\");\n    std::sort(res.begin(), res.end(), cmp_words);\n    kak_assert(eq(res, WordList{ \"allo\", \"mutch\", \"tchou\" }));\n    buffer.insert({1, 0}, \"re\");\n    res = word_db.find_matching(\"\");\n    std::sort(res.begin(), res.end(), cmp_words);\n    kak_assert(eq(res, WordList{ \"allo\", \"mutch\", \"retchou\", \"tchou\" }));\n}};\n\n}\n"
  },
  {
    "path": "src/word_db.hh",
    "content": "#ifndef word_db_hh_INCLUDED\n#define word_db_hh_INCLUDED\n\n#include \"shared_string.hh\"\n#include \"hash_map.hh\"\n#include \"vector.hh\"\n#include \"ranked_match.hh\"\n#include \"option.hh\"\n#include \"safe_ptr.hh\"\n\nnamespace Kakoune\n{\n\nusing RankedMatchList = Vector<RankedMatch>;\nclass Buffer;\n\n// maintain a database of words available in a buffer\nclass WordDB : public OptionWatcher\n{\npublic:\n    WordDB(const Buffer& buffer);\n    ~WordDB();\n    WordDB(const WordDB&) = delete;\n    WordDB(WordDB&&) noexcept;\n\n    RankedMatchList find_matching(StringView str);\n\n    int get_word_occurences(StringView word) const;\nprivate:\n    void update_db();\n    void add_words(StringView line, ConstArrayView<Codepoint> extra_word_chars);\n    void remove_words(StringView line, ConstArrayView<Codepoint> extra_word_chars);\n\n    void rebuild_db();\n\n    void on_option_changed(const Option& option) override;\n\n    struct WordInfo\n    {\n        StringDataPtr word;\n        UsedLetters letters;\n        int refcount;\n    };\n    using WordToInfo = HashMap<StringView, WordInfo, MemoryDomain::WordDB>;\n    using Lines = Vector<StringDataPtr, MemoryDomain::WordDB>;\n\n    SafePtr<const Buffer> m_buffer;\n    size_t m_timestamp;\n    WordToInfo m_words;\n    Lines m_lines;\n};\n\nWordDB& get_word_db(const Buffer& buffer);\n\n}\n\n#endif // word_db_hh_INCLUDED\n"
  },
  {
    "path": "src/word_splitter.hh",
    "content": "#ifndef word_splitter_hh_INCLUDED\n#define word_splitter_hh_INCLUDED\n\n#include \"string.hh\"\n#include \"array_view.hh\"\n\nnamespace Kakoune\n{\n\nstruct WordSplitter\n{\n    static constexpr ByteCount max_word_len = 100;\n\n    struct Iterator\n    {\n        Iterator(const char* begin, const WordSplitter& splitter)\n            : m_word_begin{begin}, m_word_end{begin}, m_splitter{&splitter}\n        { operator++(); }\n\n        StringView operator*() const { return {m_word_begin, m_word_end}; }\n\n        Iterator& operator++()\n        {\n            const auto* end = m_splitter->m_content.end();\n            auto extra_chars = m_splitter->m_extra_word_chars;\n\n            do\n            {\n                auto it = m_word_begin = m_word_end;\n                while (it != end and not is_word(utf8::read_codepoint(it, end), extra_chars))\n                    m_word_begin = it;\n\n                m_word_end = it;\n                while (it != end and is_word(utf8::read_codepoint(it, end), extra_chars))\n                    m_word_end = it;\n            } while (m_word_begin != end and (m_word_end - m_word_begin) > max_word_len);\n\n            return *this;\n        }\n\n        friend bool operator==(const Iterator& lhs, const Iterator& rhs) = default;\n\n        const char* m_word_begin;\n        const char* m_word_end;\n        const WordSplitter* m_splitter;\n    };\n\n    StringView m_content;\n    ConstArrayView<Codepoint> m_extra_word_chars;\n\n    Iterator begin() const { return {m_content.begin(), *this}; }\n    Iterator end()   const { return {m_content.end(), *this}; }\n};\n\n}\n\n#endif // word_splitter_hh_INCLUDED\n"
  },
  {
    "path": "test/README.asciidoc",
    "content": "Regression test\n===============\n\n:unified-context-diff: https://en.wikipedia.org/wiki/Diff#Unified_format\n\nSource structure\n----------------\n\n----------------------------------------------\n.\n├── unit\n│   └── …\n└── compose\n    └── …\n        ├── [enabled]    → applicability\n        ├── [env]        → environment setup\n        ├── [rc]         → configuration\n        ├── [in]         → start file\n        ├── cmd          → command\n        ├── [script]     → UI automation\n        ├── [out]        → expected end file\n        ├── [kak_*]      → expected expansion values\n        └── [error]      → expected error\n----------------------------------------------\n\nUsage\n-----\n\nTo test, just type +run [test]+ in the +test+ directory.\nIt will print each passing test.  If a test fails, a {unified-context-diff}[unified context diff]\nis printed showing the test’s expected output and the actual output.\n\nDetails\n-------\n\n+enabled+ is optional.\nIf it exists and is executable,\nit is invoked with no parameters.\nIf it exits with a non-zero exit code,\nthe test is assumed to be not applicable to the current environment\n(for example, a test for OS-specific integration\nisn't useful on a different OS)\nand will be silently skipped.\n\n+env+ is optional.\nIf it exists, it will be sourced before starting Kakoune.\n\n+rc+ is optional\nand should contain a sequence of commands,\n_e.g._, +set-option+, +define-command+, +declare-option+.\n+rc+ is sourced and evaluated before the +cmd+ key sequence is executed.\n\n+in+ is optional\nand should contain the initial text loaded into the input buffer\nfor editing by the +cmd+ key sequence.\n\n+cmd+ is required\nand should contain a key sequence that will edit the input buffer.\n+cmd+ is executed after the +rc+ command sequence is sourced.\n\n+script+ is optional\nand is a shell-script that will be sourced after +cmd+ is executed.\nThe special +ui_in+ function sends a string\n(expected to be a JSON UI message,\nsee `doc/json_ui.asciidoc` in the Kakoune source)\nto the running Kakoune instance,\nwhile the special +ui_out+ function\nchecks the next JSON UI messages from Kakoune\nagainst its arguments,\nand fails the test if any of them are different.\n\nYou can also say `ui_out -ignore N` to ignore the next _N_ JSON UI messages,\nwhere _N_ is a positive integer. \n\n+out+ is optional\nand should contain the expected text generated by the +cmd+ key sequence.\n\nIf the actual +out+ text\ndoes not match the expected content in the corresponding file,\nthe unit test will fail.\n\nIf there is no +out+\nthen the unit test will always succeed.\n\nAny +kak_*+ files should match the corresponding expansion\nafter +cmd+ is complete.\nFor example, a file named +kak_selection_desc+\nshould match the +%val{selection_desc}+ expansion.\nSee `:doc expansions` for a list of available expansions.\n\nIf there is an +error+ file,\nthe test is expected to produce an error.\nIf Kakoune exits successfully,\nor if it fails with the wrong error,\nthe test is marked as a failure.\n"
  },
  {
    "path": "test/commands/edit-fifo-noscroll/cmd",
    "content": ":nop %sh{mkfifo fifo}; edit -fifo fifo *fifo*<ret>\n"
  },
  {
    "path": "test/commands/edit-fifo-noscroll/out",
    "content": "* line1\n* line2\n2049\n"
  },
  {
    "path": "test/commands/edit-fifo-noscroll/script",
    "content": "ui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nexec 5>fifo\n\necho '* line1' >&5\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" line1\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [false] }'\n\necho '* line2' >&5\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" line1\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"* line2\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [false] }'\n\ndd if=/dev/zero bs=2049 count=1 2>/dev/null | sed s/././g >&5\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [false] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"gjxH|wc -c | tr -d \\\" \\\"<ret>\" ] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [false] }'\n\nexec 5>&-\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*fifo* 3:4 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[scratch]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\n"
  },
  {
    "path": "test/commands/edit-fifo-noscroll-noeol/cmd",
    "content": ":nop %sh{mkfifo fifo}; edit -fifo fifo *fifo*<ret>\n"
  },
  {
    "path": "test/commands/edit-fifo-noscroll-noeol/out",
    "content": "* noeol\n"
  },
  {
    "path": "test/commands/edit-fifo-noscroll-noeol/script",
    "content": "ui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nexec 5>fifo\n\nprintf '* noeol' >&5\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" noeol\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n\nexec 5>&-\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*fifo* 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[scratch]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\n"
  },
  {
    "path": "test/commands/fifo-read-ranges/cmd",
    "content": "\n"
  },
  {
    "path": "test/commands/fifo-read-ranges/rc",
    "content": "hook global BufReadFifo .* %{\n    echo -to-file ranges %val{hook_param}\n}\nnop %sh{mkfifo fifo 2>/dev/null}\nedit -fifo fifo *fifo*\n"
  },
  {
    "path": "test/commands/fifo-read-ranges/script",
    "content": "mkfifo fifo ranges 2>/dev/null\nexec 5>fifo\necho a >&5\nassert_eq 1.1,1.2 \"$(cat ranges)\"\necho b >&5\nassert_eq 2.1,2.2 \"$(cat ranges)\"\necho c >&5\nassert_eq 3.1,3.2 \"$(cat ranges)\"\nexec 5>&-\n"
  },
  {
    "path": "test/commands/fifo-read-ranges-noeol/cmd",
    "content": "\n"
  },
  {
    "path": "test/commands/fifo-read-ranges-noeol/rc",
    "content": "hook global BufReadFifo .* %{\n    echo -to-file ranges %val{hook_param}\n}\nnop %sh{mkfifo fifo 2>/dev/null}\nedit -fifo fifo *fifo*\n"
  },
  {
    "path": "test/commands/fifo-read-ranges-noeol/script",
    "content": "mkfifo fifo ranges 2>/dev/null\nexec 5>fifo\nprintf a >&5\nassert_eq 1.1,1.1 \"$(cat ranges)\"\nprintf b >&5\nassert_eq 1.2,1.2 \"$(cat ranges)\"\nprintf 'c\\n' >&5\nassert_eq 1.3,1.4 \"$(cat ranges)\"\nprintf d >&5\nassert_eq 2.1,2.1 \"$(cat ranges)\"\nexec 5>&-\n"
  },
  {
    "path": "test/commands/fifo-read-ranges-noeol-scroll/cmd",
    "content": "\n"
  },
  {
    "path": "test/commands/fifo-read-ranges-noeol-scroll/rc",
    "content": "hook global BufReadFifo .* %{\n    echo -to-file ranges %val{hook_param}\n}\nnop %sh{mkfifo fifo 2>/dev/null}\nedit -fifo fifo -scroll *fifo*\n"
  },
  {
    "path": "test/commands/fifo-read-ranges-noeol-scroll/script",
    "content": "mkfifo fifo ranges 2>/dev/null\nexec 5>fifo\nprintf a >&5\nassert_eq 1.1,1.1 \"$(cat ranges)\"\nprintf b >&5\nassert_eq 1.2,1.2 \"$(cat ranges)\"\nprintf 'c\\n' >&5\nassert_eq 1.3,1.4 \"$(cat ranges)\"\nprintf d >&5\nassert_eq 2.1,2.1 \"$(cat ranges)\"\nexec 5>&-\n"
  },
  {
    "path": "test/commands/fifo-read-ranges-scroll/cmd",
    "content": "\n"
  },
  {
    "path": "test/commands/fifo-read-ranges-scroll/rc",
    "content": "hook global BufReadFifo .* %{\n    echo -to-file ranges %val{hook_param}\n}\nnop %sh{mkfifo fifo 2>/dev/null}\nedit -fifo fifo -scroll *fifo*\n"
  },
  {
    "path": "test/commands/fifo-read-ranges-scroll/script",
    "content": "mkfifo fifo ranges 2>/dev/null\nexec 5>fifo\necho a >&5\nassert_eq 1.1,1.2 \"$(cat ranges)\"\necho b >&5\nassert_eq 2.1,2.2 \"$(cat ranges)\"\necho c >&5\nassert_eq 3.1,3.2 \"$(cat ranges)\"\nexec 5>&-\n"
  },
  {
    "path": "test/compose/backward-search/cmd",
    "content": "<a-/>xxx<ret>\n"
  },
  {
    "path": "test/compose/backward-search/in",
    "content": "xxx%(y)yyxxx\n"
  },
  {
    "path": "test/compose/backward-search/kak_selections_desc",
    "content": "1.1,1.3\n"
  },
  {
    "path": "test/compose/catch-error-desc/cmd",
    "content": ":error<ret>\n"
  },
  {
    "path": "test/compose/catch-error-desc/out",
    "content": "1:2: 'non-existing-command': no such command\n"
  },
  {
    "path": "test/compose/catch-error-desc/rc",
    "content": "define-command error %{ try %{ non-existing-command } catch %{ exec i \"%val{error}\" <esc> } }\n"
  },
  {
    "path": "test/compose/complex-pipe/cmd",
    "content": "|sort<ret>\n"
  },
  {
    "path": "test/compose/complex-pipe/in",
    "content": "foo %(baz\nbar\nqux) quux\n\nfoo %(baz\nbar\nqux\n)\n"
  },
  {
    "path": "test/compose/complex-pipe/kak_quoted_selections",
    "content": "'bar\nbaz\nqux' 'bar\nbaz\nqux\n'\n"
  },
  {
    "path": "test/compose/complex-pipe/out",
    "content": "foo bar\nbaz\nqux quux\n\nfoo bar\nbaz\nqux\n\n"
  },
  {
    "path": "test/compose/del-empty-line/cmd",
    "content": "CCdd\n"
  },
  {
    "path": "test/compose/del-empty-line/in",
    "content": "asdf\n\nasdf\n"
  },
  {
    "path": "test/compose/del-empty-line/out",
    "content": "df\ndf\n"
  },
  {
    "path": "test/compose/discard-selections-with-itersel/cmd",
    "content": ":exec -itersel <lt>a-k>ba<lt>ret><ret>d\n"
  },
  {
    "path": "test/compose/discard-selections-with-itersel/in",
    "content": "%(foo)\n%(bar)\n%(baz)\n"
  },
  {
    "path": "test/compose/discard-selections-with-itersel/out",
    "content": "foo\n\n\n"
  },
  {
    "path": "test/compose/do-not-run-hooks-added-by-parent/cmd",
    "content": "<ret><ret>\n"
  },
  {
    "path": "test/compose/do-not-run-hooks-added-by-parent/out",
    "content": "foo\n"
  },
  {
    "path": "test/compose/do-not-run-hooks-added-by-parent/rc",
    "content": "hook global NormalKey <ret> %{\n    hook window NormalKey <ret> %{\n        exec -draft ifoo<esc> \n    }\n}\n"
  },
  {
    "path": "test/compose/echo-to-file/cmd",
    "content": ":echo -to-file data %{foo bar}<ret>!cat data<ret>\n"
  },
  {
    "path": "test/compose/echo-to-file/out",
    "content": "foo bar\n"
  },
  {
    "path": "test/compose/eof-multi-delete/cmd",
    "content": "%<a-s><a-K>keep<ret>d\n"
  },
  {
    "path": "test/compose/eof-multi-delete/in",
    "content": "delete\nkeep\ndelete\nkeep\ndelete\ndelete\n"
  },
  {
    "path": "test/compose/eof-multi-delete/out",
    "content": "keep\nkeep\n"
  },
  {
    "path": "test/compose/eol-at-eof/opt-in-no-eol/cmd",
    "content": ":set buffer finaleol present<ret>\n"
  },
  {
    "path": "test/compose/eol-at-eof/opt-in-no-eol/in",
    "content": "EOL at EOF will be added"
  },
  {
    "path": "test/compose/eol-at-eof/opt-in-no-eol/out",
    "content": "EOL at EOF will be added\n"
  },
  {
    "path": "test/compose/eol-at-eof/opt-out-no-eol/cmd",
    "content": ":set buffer finaleol missing<ret>\n"
  },
  {
    "path": "test/compose/eol-at-eof/opt-out-no-eol/in",
    "content": "EOL at EOF will be removed\n"
  },
  {
    "path": "test/compose/eol-at-eof/opt-out-no-eol/out",
    "content": "EOL at EOF will be removed"
  },
  {
    "path": "test/compose/eol-at-eof/write-after-insert-into-empty-file/cmd",
    "content": "iinsertion makes the buffer non-empty, so we add an EOL at EOF\n"
  },
  {
    "path": "test/compose/eol-at-eof/write-after-insert-into-empty-file/in",
    "content": ""
  },
  {
    "path": "test/compose/eol-at-eof/write-after-insert-into-empty-file/out",
    "content": "insertion makes the buffer non-empty, so we add an EOL at EOF\n"
  },
  {
    "path": "test/compose/eol-at-eof/write-empty-file/cmd",
    "content": "\n"
  },
  {
    "path": "test/compose/eol-at-eof/write-empty-file/in",
    "content": ""
  },
  {
    "path": "test/compose/eol-at-eof/write-empty-file/out",
    "content": ""
  },
  {
    "path": "test/compose/eol-at-eof/write-no-eol/cmd",
    "content": "\n"
  },
  {
    "path": "test/compose/eol-at-eof/write-no-eol/in",
    "content": "missing EOL at EOF\nwill be preserved"
  },
  {
    "path": "test/compose/eol-at-eof/write-no-eol/out",
    "content": "missing EOL at EOF\nwill be preserved"
  },
  {
    "path": "test/compose/file-expansion/cmd",
    "content": ":echo -to-file commands 'foo bar'<ret>:execute-keys i %file{commands} <lt>esc><ret>\n"
  },
  {
    "path": "test/compose/file-expansion/out",
    "content": "foo bar\n"
  },
  {
    "path": "test/compose/history/cmd",
    "content": "Amiddle<esc><a-f>dd\n"
  },
  {
    "path": "test/compose/history/in",
    "content": "start\n"
  },
  {
    "path": "test/compose/history/kak_quoted_history",
    "content": "'-' '$timestamp' '1' '0' '$timestamp' '-' '+0.5|m' '+0.6|i' '+0.7|d' '+0.8|d' '+0.9|l' '+0.10|e' '-0.8|dle'\n"
  },
  {
    "path": "test/compose/history/rc",
    "content": "# Make our expansion have a predictable timestamp\nhook global ClientClose .* %{\n    nop %sh{\n        kak -f 'ghf<space>lwc$timestamp<esc>3f<space>lwc$timestamp<esc>' kak_quoted_history\n    }\n}\n"
  },
  {
    "path": "test/compose/hook-named-captures/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/compose/hook-named-captures/out",
    "content": "ret\n"
  },
  {
    "path": "test/compose/hook-named-captures/rc",
    "content": "hook global InsertKey '<(?<name>\\w+)>' %{ exec \"<backspace>%val(hook_param_capture_name)\" }\n"
  },
  {
    "path": "test/compose/inline-sort/cmd",
    "content": "<a-i>bs<space><ret>c<ret><esc><a-i>b|sort<ret><a-j>\n"
  },
  {
    "path": "test/compose/inline-sort/in",
    "content": "(e d c b a)\n"
  },
  {
    "path": "test/compose/inline-sort/out",
    "content": "(a b c d e)\n"
  },
  {
    "path": "test/compose/line-completion/cmd",
    "content": "gjA<c-x>l<c-n><c-n><esc>\n"
  },
  {
    "path": "test/compose/line-completion/in",
    "content": "            w111111\n\n    w222222\n                          \n"
  },
  {
    "path": "test/compose/line-completion/out",
    "content": "            w111111\n\n    w222222\n                          w222222\n"
  },
  {
    "path": "test/compose/no-hook-on-unset-option-with-same-parent/cmd",
    "content": "\n"
  },
  {
    "path": "test/compose/no-hook-on-unset-option-with-same-parent/out",
    "content": "foo\n"
  },
  {
    "path": "test/compose/no-hook-on-unset-option-with-same-parent/rc",
    "content": "decl str foo\nhook global BufSetOption foo=.* %{ exec ifoo<esc> }\nset-option buffer foo value # triggers hook\nset-option global foo value # does not trigger hook\nunset-option buffer foo # does not trigger hook\n"
  },
  {
    "path": "test/compose/pipe-at-eof/cmd",
    "content": "%|sort<ret>\n"
  },
  {
    "path": "test/compose/pipe-at-eof/in",
    "content": "tchou\ntchaa\ntchii\n"
  },
  {
    "path": "test/compose/pipe-at-eof/out",
    "content": "tchaa\ntchii\ntchou\n"
  },
  {
    "path": "test/compose/select-codepoints/cmd",
    "content": "\n"
  },
  {
    "path": "test/compose/select-codepoints/in",
    "content": "😄😊😉😍😘😚😜😝😳😁😣😢😂😭😪😥😰😩\n"
  },
  {
    "path": "test/compose/select-codepoints/kak_selections_char_desc",
    "content": "1.2,1.4\n"
  },
  {
    "path": "test/compose/select-codepoints/kak_selections_desc",
    "content": "1.5,1.13\n"
  },
  {
    "path": "test/compose/select-codepoints/rc",
    "content": "select -codepoint 1.2,1.4\n"
  },
  {
    "path": "test/compose/select-display-columns/cmd",
    "content": "\n"
  },
  {
    "path": "test/compose/select-display-columns/enabled",
    "content": "#!/bin/sh\n[ $(echo -n \"😄😊😉😍\" | wc -m) = '4' ] && locale | grep LC_CTYPE | grep -qi 'utf-*8'\n"
  },
  {
    "path": "test/compose/select-display-columns/in",
    "content": "！＂＃＄％＆＇（\n"
  },
  {
    "path": "test/compose/select-display-columns/kak_selections_char_desc",
    "content": "1.2,1.4\n"
  },
  {
    "path": "test/compose/select-display-columns/kak_selections_desc",
    "content": "1.4,1.10\n"
  },
  {
    "path": "test/compose/select-display-columns/kak_selections_display_column_desc",
    "content": "1.3,1.7\n"
  },
  {
    "path": "test/compose/select-display-columns/rc",
    "content": "select -display-column 1.3,1.8\n"
  },
  {
    "path": "test/compose/select-timestamp/cmd",
    "content": "jxd:select -timestamp 1 3.1,3.2<ret>\n"
  },
  {
    "path": "test/compose/select-timestamp/in",
    "content": "aa\nbb\ncc\ndd\n"
  },
  {
    "path": "test/compose/select-timestamp/kak_quoted_selections",
    "content": "'cc'\n"
  },
  {
    "path": "test/compose/selections_char_desc-expansion/cmd",
    "content": ""
  },
  {
    "path": "test/compose/selections_char_desc-expansion/in",
    "content": "sin%(gle)-byte\n%(λx.)λy.y x - start on wide char\n\t∃e∈%(C.∀)g∈C.g⊗e≡e⊗g≡e - tabs, ending on wide char\n\t%(x̦̏)- combining characters\n"
  },
  {
    "path": "test/compose/selections_char_desc-expansion/kak_selections_char_desc",
    "content": "4.2,4.4 1.4,1.6 2.1,2.3 3.5,3.7\n"
  },
  {
    "path": "test/compose/undo-multi-replace-at-end/cmd",
    "content": "%<a-s>y1<a-,>Ru\n"
  },
  {
    "path": "test/compose/undo-multi-replace-at-end/in",
    "content": "line 1\nline 2\nline 3\n"
  },
  {
    "path": "test/compose/undo-multi-replace-at-end/kak_quoted_selections",
    "content": "'line 2\nline 3'\n"
  },
  {
    "path": "test/compose/unicode/cmd",
    "content": "~\n"
  },
  {
    "path": "test/compose/unicode/enabled",
    "content": "#!/bin/sh\nlocale | grep LC_CTYPE | grep -qi 'utf-*8'\n"
  },
  {
    "path": "test/compose/unicode/in",
    "content": "%(maïs mélange bientôt)\n"
  },
  {
    "path": "test/compose/unicode/out",
    "content": "MAÏS MÉLANGE BIENTÔT\n"
  },
  {
    "path": "test/display/horizontal-scroll/cmd",
    "content": "gl\n"
  },
  {
    "path": "test/display/horizontal-scroll/in",
    "content": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxx\n"
  },
  {
    "path": "test/display/horizontal-scroll/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"x\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"xxxxxxxxxx\\u000a\" }]], { \"line\": 0, \"column\": 79 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:90 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/display/horizontal-scroll-onto-tab/cmd",
    "content": "j2f<tab>;\n"
  },
  {
    "path": "test/display/horizontal-scroll-onto-tab/in",
    "content": "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n1234567890123456789012345678901234567890123456789012345678901234567890123456\t\t90\n\t90\t7890\n"
  },
  {
    "path": "test/display/horizontal-scroll-onto-tab/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"90123456789012345678901234567890123456789012345678901234567890123456789012345678\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"90123456789012345678901234567890123456789012345678901234567890123456\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"        \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"90\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"      \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"7890\\u000a\" }]], { \"line\": 1, \"column\": 72 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 2:78 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/display/horizontal-scroll-with-tab/cmd",
    "content": "gl\n"
  },
  {
    "path": "test/display/horizontal-scroll-with-tab/in",
    "content": "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n\t90\t7890\n"
  },
  {
    "path": "test/display/horizontal-scroll-with-tab/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1234567890123456789012345678901234567890123456789012345678901234567890123456789\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"      \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"7890\\u000a\" }]], { \"line\": 0, \"column\": 79 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:90 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/display/vertical-scroll/cmd",
    "content": "gj\n"
  },
  {
    "path": "test/display/vertical-scroll/in",
    "content": "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n"
  },
  {
    "path": "test/display/vertical-scroll/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"07\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"08\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"09\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"28\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"29\\u000a\" }], [{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"3\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\\u000a\" }]], { \"line\": 23, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 30:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/column/multi-columns/cmd",
    "content": ""
  },
  {
    "path": "test/highlight/column/multi-columns/in",
    "content": "a\n\tab\nabc\nabcd\n"
  },
  {
    "path": "test/highlight/column/multi-columns/rc",
    "content": "add-highlighter window/ number-lines\nset window tabstop 4\nadd-highlighter window/ column 3 default,red\nadd-highlighter window/ column 7 default,green\nadd-highlighter window/ column 5 default,blue\n"
  },
  {
    "path": "test/highlight/column/multi-columns/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"green\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 2│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"  \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"green\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 3│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ab\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"c\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"green\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 4│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ab\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"c\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"d\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"green\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }]], { \"line\": 0, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\n"
  },
  {
    "path": "test/highlight/face-override/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/face-override/in",
    "content": "foo\n"
  },
  {
    "path": "test/highlight/face-override/rc",
    "content": "face global Foo default,blue+i\nface buffer Foo green,default+u@Foo\nface buffer Foo red@Foo\nadd-highlighter window/ regex foo 0:Foo\n"
  },
  {
    "path": "test/highlight/face-override/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"underline\",\"italic\"] }, \"contents\": \"f\" }, { \"face\": { \"fg\": \"red\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [\"underline\",\"italic\"] }, \"contents\": \"oo\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/named-captures/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/named-captures/in",
    "content": "2018-01-03\n"
  },
  {
    "path": "test/highlight/named-captures/rc",
    "content": "add-highlighter window/ regex (?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+) year:red month:green day:yellow\n"
  },
  {
    "path": "test/highlight/named-captures/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2\" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"018\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-\" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"01\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"03\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/number-lines/basic/cmd",
    "content": "gl\n"
  },
  {
    "path": "test/highlight/number-lines/basic/in",
    "content": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
  },
  {
    "path": "test/highlight/number-lines/basic/rc",
    "content": "add-highlighter window/ number-lines\n"
  },
  {
    "path": "test/highlight/number-lines/basic/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"x\" }]], { \"line\": 0, \"column\": 79 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\n"
  },
  {
    "path": "test/highlight/number-lines/full-relative/cmd",
    "content": "vv\n"
  },
  {
    "path": "test/highlight/number-lines/full-relative/in",
    "content": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n108\n109\n110\n111\n112\n113\n114\n115\n116\n117\n118\n119\n120\n121\n122\n123\n124\n125\n126\n127\n128\n129\n130\n131\n132\n133\n134\n135\n136\n137\n138\n139\n140\n141\n142\n143\n144\n145\n146\n147\n148\n149\n150\n151\n152\n153\n154\n155\n156\n157\n158\n159\n160\n161\n162\n163\n164\n165\n166\n167\n168\n169\n170\n171\n172\n173\n174\n175\n176\n177\n178\n179\n180\n181\n182\n183\n184\n185\n186\n187\n188\n189\n190\n191\n192\n193\n194\n195\n196\n197\n198\n199\n200\n201\n202\n203\n204\n205\n206\n207\n208\n209\n210\n211\n212\n213\n214\n215\n216\n217\n218\n219\n220\n221\n222\n223\n224\n225\n226\n227\n228\n229\n230\n231\n232\n233\n234\n235\n236\n237\n238\n239\n240\n241\n242\n243\n244\n245\n246\n247\n248\n249\n250\n251\n252\n253\n254\n255\n256\n257\n258\n259\n260\n261\n262\n263\n264\n265\n266\n267\n268\n269\n270\n271\n272\n273\n274\n275\n276\n277\n278\n279\n280\n281\n282\n283\n284\n285\n286\n287\n288\n289\n290\n291\n292\n293\n294\n295\n296\n297\n298\n299\n300\n301\n302\n303\n304\n305\n306\n307\n308\n309\n310\n311\n312\n313\n314\n315\n316\n317\n318\n319\n320\n321\n322\n323\n324\n325\n326\n327\n328\n329\n330\n331\n332\n333\n334\n335\n336\n337\n338\n339\n340\n341\n342\n343\n344\n345\n346\n347\n348\n349\n350\n351\n352\n353\n354\n355\n356\n357\n358\n359\n360\n361\n362\n363\n364\n365\n366\n367\n368\n369\n370\n371\n372\n373\n374\n375\n376\n377\n378\n379\n380\n381\n382\n383\n384\n385\n386\n387\n388\n389\n390\n391\n392\n393\n394\n395\n396\n397\n398\n399\n400\n401\n402\n403\n404\n405\n406\n407\n408\n409\n410\n411\n412\n413\n414\n415\n416\n417\n418\n419\n420\n421\n422\n423\n424\n425\n426\n427\n428\n429\n430\n431\n432\n433\n434\n435\n436\n437\n438\n439\n440\n441\n442\n443\n444\n445\n446\n447\n448\n449\n450\n451\n452\n453\n454\n455\n456\n457\n458\n459\n460\n461\n462\n463\n464\n465\n466\n467\n468\n469\n470\n471\n472\n473\n474\n475\n476\n477\n478\n479\n480\n481\n482\n483\n484\n485\n486\n487\n488\n489\n490\n491\n492\n493\n494\n495\n496\n497\n498\n499\n500\n501\n502\n503\n504\n505\n506\n507\n508\n509\n510\n511\n512\n513\n514\n515\n516\n517\n518\n519\n520\n521\n522\n523\n524\n525\n526\n527\n528\n529\n530\n531\n532\n533\n534\n535\n536\n537\n538\n539\n540\n541\n542\n543\n544\n545\n546\n547\n548\n549\n550\n551\n552\n553\n554\n555\n556\n557\n558\n559\n560\n561\n562\n563\n564\n565\n566\n567\n568\n569\n570\n571\n572\n573\n574\n575\n576\n577\n578\n579\n580\n581\n582\n583\n584\n585\n586\n587\n588\n589\n590\n591\n592\n593\n594\n595\n596\n597\n598\n599\n600\n601\n602\n603\n604\n605\n606\n607\n608\n609\n610\n611\n612\n613\n614\n615\n616\n617\n618\n619\n620\n621\n622\n623\n624\n625\n626\n627\n628\n629\n630\n631\n632\n633\n634\n635\n636\n637\n638\n639\n640\n641\n642\n643\n644\n645\n646\n647\n648\n649\n650\n651\n652\n653\n654\n655\n656\n657\n658\n659\n660\n661\n662\n663\n664\n665\n666\n667\n668\n669\n670\n671\n672\n673\n674\n675\n676\n677\n678\n679\n680\n681\n682\n683\n684\n685\n686\n687\n688\n689\n690\n691\n692\n693\n694\n695\n696\n697\n698\n699\n700\n701\n702\n703\n704\n705\n706\n707\n708\n709\n710\n711\n712\n713\n714\n715\n716\n717\n718\n719\n720\n721\n722\n723\n724\n725\n726\n727\n728\n729\n730\n731\n732\n733\n734\n735\n736\n737\n738\n739\n740\n741\n742\n743\n744\n745\n746\n747\n748\n749\n750\n751\n752\n753\n754\n755\n756\n757\n758\n759\n760\n761\n762\n763\n764\n765\n766\n767\n768\n769\n770\n771\n772\n773\n774\n775\n776\n777\n778\n779\n780\n781\n782\n783\n784\n785\n786\n787\n788\n789\n790\n791\n792\n793\n794\n795\n796\n797\n798\n799\n800\n801\n802\n803\n804\n805\n806\n807\n808\n809\n810\n811\n812\n813\n814\n815\n816\n817\n818\n819\n820\n821\n822\n823\n824\n825\n826\n827\n828\n829\n830\n831\n832\n833\n834\n835\n836\n837\n838\n839\n840\n841\n842\n843\n844\n845\n846\n847\n848\n849\n850\n851\n852\n853\n854\n855\n856\n857\n858\n859\n860\n861\n862\n863\n864\n865\n866\n867\n868\n869\n870\n871\n872\n873\n874\n875\n876\n877\n878\n879\n880\n881\n882\n883\n884\n885\n886\n887\n888\n889\n890\n891\n892\n893\n894\n895\n896\n897\n898\n899\n900\n901\n902\n903\n904\n905\n906\n907\n908\n909\n910\n911\n912\n913\n914\n915\n916\n917\n918\n919\n920\n921\n922\n923\n924\n925\n926\n927\n928\n929\n930\n931\n932\n933\n934\n935\n936\n937\n938\n939\n940\n941\n942\n943\n944\n945\n946\n947\n948\n949\n950\n951\n952\n953\n954\n955\n956\n957\n958\n959\n960\n961\n962\n963\n964\n965\n966\n967\n968\n969\n970\n971\n972\n973\n974\n975\n976\n977\n978\n979\n980\n981\n982\n983\n984\n985\n986\n987\n988\n989\n990\n991\n992\n993\n994\n995\n996\n997\n998\n999\n1000\n1001\n1002\n1003\n1004\n1005\n1006\n1007\n1008\n1009\n1010\n1011\n1012\n1013\n1014\n1015\n1016\n1017\n1018\n1019\n1020\n1021\n1022\n1023\n1024\n1025\n1026\n1027\n1028\n1029\n1030\n1031\n1032\n1033\n1034\n1035\n1036\n1037\n1038\n1039\n1040\n1041\n1042\n1043\n1044\n1045\n1046\n1047\n1048\n1049\n1050\n1051\n1052\n1053\n1054\n1055\n1056\n1057\n1058\n1059\n1060\n1061\n1062\n1063\n1064\n1065\n1066\n1067\n1068\n1069\n1070\n1071\n1072\n1073\n1074\n1075\n1076\n1077\n1078\n1079\n1080\n1081\n1082\n1083\n1084\n1085\n1086\n1087\n1088\n1089\n1090\n1091\n1092\n1093\n1094\n1095\n1096\n1097\n1098\n1099\n1100\n1101\n1102\n1103\n1104\n1105\n1106\n1107\n1108\n1109\n1110\n1111\n1112\n1113\n1114\n1115\n1116\n1117\n1118\n1119\n1120\n1121\n1122\n1123\n1124\n1125\n1126\n1127\n1128\n1129\n1130\n1131\n1132\n1133\n1134\n1135\n1136\n1137\n1138\n1139\n1140\n1141\n1142\n1143\n1144\n1145\n1146\n1147\n1148\n1149\n1150\n1151\n1152\n1153\n1154\n1155\n1156\n1157\n1158\n1159\n1160\n1161\n1162\n1163\n1164\n1165\n1166\n1167\n1168\n1169\n1170\n1171\n1172\n1173\n1174\n1175\n1176\n1177\n1178\n1179\n1180\n1181\n1182\n1183\n1184\n1185\n1186\n1187\n1188\n1189\n1190\n1191\n1192\n1193\n1194\n1195\n1196\n1197\n1198\n1199\n1200\n1201\n1202\n1203\n1204\n1205\n1206\n1207\n1208\n1209\n1210\n1211\n1212\n1213\n1214\n1215\n1216\n1217\n1218\n1219\n1220\n1221\n1222\n1223\n1224\n1225\n1226\n1227\n1228\n1229\n1230\n1231\n1232\n1233\n1234\n1235\n1236\n1237\n1238\n1239\n1240\n1241\n1242\n1243\n1244\n1245\n1246\n1247\n1248\n1249\n1250\n1251\n1252\n1253\n1254\n1255\n1256\n1257\n1258\n1259\n1260\n1261\n1262\n1263\n1264\n1265\n1266\n1267\n1268\n1269\n1270\n1271\n1272\n1273\n1274\n1275\n1276\n1277\n1278\n1279\n1280\n1281\n1282\n1283\n1284\n1285\n1286\n1287\n1288\n1289\n1290\n1291\n1292\n1293\n1294\n1295\n1296\n1297\n1298\n1299\n1300\n1301\n1302\n1303\n1304\n1305\n1306\n1307\n1308\n1309\n1310\n1311\n1312\n1313\n1314\n1315\n1316\n1317\n1318\n1319\n1320\n1321\n1322\n1323\n1324\n1325\n1326\n1327\n1328\n1329\n1330\n1331\n1332\n1333\n1334\n1335\n1336\n1337\n1338\n1339\n1340\n1341\n1342\n1343\n1344\n1345\n1346\n1347\n1348\n1349\n1350\n1351\n1352\n1353\n1354\n1355\n1356\n1357\n1358\n1359\n1360\n1361\n1362\n1363\n1364\n1365\n1366\n1367\n1368\n1369\n1370\n1371\n1372\n1373\n1374\n1375\n1376\n1377\n1378\n1379\n1380\n1381\n1382\n1383\n1384\n1385\n1386\n1387\n1388\n1389\n1390\n1391\n1392\n1393\n1394\n1395\n1396\n1397\n1398\n1399\n1400\n1401\n1402\n1403\n1404\n1405\n1406\n1407\n1408\n1409\n1410\n1411\n1412\n1413\n1414\n1415\n1416\n1417\n1418\n1419\n1420\n1421\n1422\n1423\n1424\n1425\n1426\n1427\n1428\n1429\n1430\n1431\n1432\n1433\n1434\n1435\n1436\n1437\n1438\n1439\n1440\n1441\n1442\n1443\n1444\n1445\n1446\n1447\n1448\n1449\n1450\n1451\n1452\n1453\n1454\n1455\n1456\n1457\n1458\n1459\n1460\n1461\n1462\n1463\n1464\n1465\n1466\n1467\n1468\n1469\n1470\n1471\n1472\n1473\n1474\n1475\n1476\n1477\n1478\n1479\n1480\n1481\n1482\n1483\n1484\n1485\n1486\n1487\n1488\n1489\n1490\n1491\n1492\n1493\n1494\n1495\n1496\n1497\n1498\n1499\n1500\n1501\n1502\n1503\n1504\n1505\n1506\n1507\n1508\n1509\n1510\n1511\n1512\n1513\n1514\n1515\n1516\n1517\n1518\n1519\n1520\n1521\n1522\n1523\n1524\n1525\n1526\n1527\n1528\n1529\n1530\n1531\n1532\n1533\n1534\n1535\n1536\n1537\n1538\n1539\n1540\n1541\n1542\n1543\n1544\n1545\n1546\n1547\n1548\n1549\n1550\n1551\n1552\n1553\n1554\n1555\n1556\n1557\n1558\n1559\n1560\n1561\n1562\n1563\n1564\n1565\n1566\n1567\n1568\n1569\n1570\n1571\n1572\n1573\n1574\n1575\n1576\n1577\n1578\n1579\n1580\n1581\n1582\n1583\n1584\n1585\n1586\n1587\n1588\n1589\n1590\n1591\n1592\n1593\n1594\n1595\n1596\n1597\n1598\n1599\n1600\n1601\n1602\n1603\n1604\n1605\n1606\n1607\n1608\n1609\n1610\n1611\n1612\n1613\n1614\n1615\n1616\n1617\n1618\n1619\n1620\n1621\n1622\n1623\n1624\n1625\n1626\n1627\n1628\n1629\n1630\n1631\n1632\n1633\n1634\n1635\n1636\n1637\n1638\n1639\n1640\n1641\n1642\n1643\n1644\n1645\n1646\n1647\n1648\n1649\n1650\n1651\n1652\n1653\n1654\n1655\n1656\n1657\n1658\n1659\n1660\n1661\n1662\n1663\n1664\n1665\n1666\n1667\n1668\n1669\n1670\n1671\n1672\n1673\n1674\n1675\n1676\n1677\n1678\n1679\n1680\n1681\n1682\n1683\n1684\n1685\n1686\n1687\n1688\n1689\n1690\n1691\n1692\n1693\n1694\n1695\n1696\n1697\n1698\n1699\n1700\n1701\n1702\n1703\n1704\n1705\n1706\n1707\n1708\n1709\n1710\n1711\n1712\n1713\n1714\n1715\n1716\n1717\n1718\n1719\n1720\n1721\n1722\n1723\n1724\n1725\n1726\n1727\n1728\n1729\n1730\n1731\n1732\n1733\n1734\n1735\n1736\n1737\n1738\n1739\n1740\n1741\n1742\n1743\n1744\n1745\n1746\n1747\n1748\n1749\n1750\n1751\n1752\n1753\n1754\n1755\n1756\n1757\n1758\n1759\n1760\n1761\n1762\n1763\n1764\n1765\n1766\n1767\n1768\n1769\n1770\n1771\n1772\n1773\n1774\n1775\n1776\n1777\n1778\n1779\n1780\n1781\n1782\n1783\n1784\n1785\n1786\n1787\n1788\n1789\n1790\n1791\n1792\n1793\n1794\n1795\n1796\n1797\n1798\n1799\n1800\n1801\n1802\n1803\n1804\n1805\n1806\n1807\n1808\n1809\n1810\n1811\n1812\n1813\n1814\n1815\n1816\n1817\n1818\n1819\n1820\n1821\n1822\n1823\n1824\n1825\n1826\n1827\n1828\n1829\n1830\n1831\n1832\n1833\n1834\n1835\n1836\n1837\n1838\n1839\n1840\n1841\n1842\n1843\n1844\n1845\n1846\n1847\n1848\n1849\n1850\n1851\n1852\n1853\n1854\n1855\n1856\n1857\n1858\n1859\n1860\n1861\n1862\n1863\n1864\n1865\n1866\n1867\n1868\n1869\n1870\n1871\n1872\n1873\n1874\n1875\n1876\n1877\n1878\n1879\n1880\n1881\n1882\n1883\n1884\n1885\n1886\n1887\n1888\n1889\n1890\n1891\n1892\n1893\n1894\n1895\n1896\n1897\n1898\n1899\n1900\n1901\n1902\n1903\n1904\n1905\n1906\n1907\n1908\n1909\n1910\n1911\n1912\n1913\n1914\n1915\n1916\n1917\n1918\n1919\n1920\n1921\n1922\n1923\n1924\n1925\n1926\n1927\n1928\n1929\n1930\n1931\n1932\n1933\n1934\n1935\n1936\n1937\n1938\n1939\n1940\n1941\n1942\n1943\n1944\n1945\n1946\n1947\n1948\n1949\n1950\n1951\n1952\n1953\n1954\n1955\n1956\n1957\n1958\n1959\n1960\n1961\n1962\n1963\n1964\n1965\n1966\n1967\n1968\n1969\n1970\n1971\n1972\n1973\n1974\n1975\n1976\n1977\n1978\n1979\n1980\n1981\n1982\n1983\n1984\n1985\n1986\n1987\n1988\n1989\n1990\n1991\n1992\n1993\n1994\n1995\n1996\n1997\n1998\n1999\n2000\n2001\n2002\n2003\n2004\n2005\n2006\n2007\n2008\n2009\n2010\n2011\n2012\n2013\n2014\n2015\n2016\n2017\n2018\n2019\n2020\n2021\n2022\n2023\n2024\n2025\n2026\n2027\n2028\n2029\n2030\n2031\n2032\n2033\n2034\n2035\n2036\n2037\n2038\n2039\n2040\n2041\n2042\n2043\n2044\n2045\n2046\n2047\n2048\n2049\n2050\n2051\n2052\n2053\n2054\n2055\n2056\n2057\n2058\n2059\n2060\n2061\n2062\n2063\n2064\n2065\n2066\n2067\n2068\n2069\n2070\n2071\n2072\n2073\n2074\n2075\n2076\n2077\n2078\n2079\n2080\n2081\n2082\n2083\n2084\n2085\n2086\n2087\n2088\n2089\n2090\n2091\n2092\n2093\n2094\n2095\n2096\n2097\n2098\n2099\n2100\n2101\n2102\n2103\n2104\n2105\n2106\n2107\n2108\n2109\n2110\n2111\n2112\n2113\n2114\n2115\n2116\n2117\n2118\n2119\n2120\n2121\n2122\n2123\n2124\n2125\n2126\n2127\n2128\n2129\n2130\n2131\n2132\n2133\n2134\n2135\n2136\n2137\n2138\n2139\n2140\n2141\n2142\n2143\n2144\n2145\n2146\n2147\n2148\n2149\n2150\n2151\n2152\n2153\n2154\n2155\n2156\n2157\n2158\n2159\n2160\n2161\n2162\n2163\n2164\n2165\n2166\n2167\n2168\n2169\n2170\n2171\n2172\n2173\n2174\n2175\n2176\n2177\n2178\n2179\n2180\n2181\n2182\n2183\n2184\n2185\n2186\n2187\n2188\n2189\n2190\n2191\n2192\n2193\n2194\n2195\n2196\n2197\n2198\n2199\n2200\n2201\n2202\n2203\n2204\n2205\n2206\n2207\n2208\n2209\n2210\n2211\n2212\n2213\n2214\n2215\n2216\n2217\n2218\n2219\n2220\n2221\n2222\n2223\n2224\n2225\n2226\n2227\n2228\n2229\n2230\n2231\n2232\n2233\n2234\n2235\n2236\n2237\n2238\n2239\n2240\n2241\n2242\n2243\n2244\n2245\n2246\n2247\n2248\n2249\n2250\n2251\n2252\n2253\n2254\n2255\n2256\n2257\n2258\n2259\n2260\n2261\n2262\n2263\n2264\n2265\n2266\n2267\n2268\n2269\n2270\n2271\n2272\n2273\n2274\n2275\n2276\n2277\n2278\n2279\n2280\n2281\n2282\n2283\n2284\n2285\n2286\n2287\n2288\n2289\n2290\n2291\n2292\n2293\n2294\n2295\n2296\n2297\n2298\n2299\n2300\n2301\n2302\n2303\n2304\n2305\n2306\n2307\n2308\n2309\n2310\n2311\n2312\n2313\n2314\n2315\n2316\n2317\n2318\n2319\n2320\n2321\n2322\n2323\n2324\n2325\n2326\n2327\n2328\n2329\n2330\n2331\n2332\n2333\n2334\n2335\n2336\n2337\n2338\n2339\n2340\n2341\n2342\n2343\n2344\n2345\n2346\n2347\n2348\n2349\n2350\n2351\n2352\n2353\n2354\n2355\n2356\n2357\n2358\n2359\n2360\n2361\n2362\n2363\n2364\n2365\n2366\n2367\n2368\n2369\n2370\n2371\n2372\n2373\n2374\n2375\n2376\n2377\n2378\n2379\n2380\n2381\n2382\n2383\n2384\n2385\n2386\n2387\n2388\n2389\n2390\n2391\n2392\n2393\n2394\n2395\n2396\n2397\n2398\n2399\n2400\n2401\n2402\n2403\n2404\n2405\n2406\n2407\n2408\n2409\n2410\n2411\n2412\n2413\n2414\n2415\n2416\n2417\n2418\n2419\n2420\n2421\n2422\n2423\n2424\n2425\n2426\n2427\n2428\n2429\n2430\n2431\n2432\n2433\n2434\n2435\n2436\n2437\n2438\n2439\n2440\n2441\n2442\n2443\n2444\n2445\n2446\n2447\n2448\n2449\n2450\n2451\n2452\n2453\n2454\n2455\n2456\n2457\n2458\n2459\n2460\n2461\n2462\n2463\n2464\n2465\n2466\n2467\n2468\n2469\n2470\n2471\n2472\n2473\n2474\n2475\n2476\n2477\n2478\n2479\n2480\n2481\n2482\n2483\n2484\n2485\n2486\n2487\n2488\n2489\n2490\n2491\n2492\n2493\n2494\n2495\n2496\n2497\n2498\n2499\n2500\n2501\n2502\n2503\n2504\n2505\n2506\n2507\n2508\n2509\n2510\n2511\n2512\n2513\n2514\n2515\n2516\n2517\n2518\n2519\n2520\n2521\n2522\n2523\n2524\n2525\n2526\n2527\n2528\n2529\n2530\n2531\n2532\n2533\n2534\n2535\n2536\n2537\n2538\n2539\n2540\n2541\n2542\n2543\n2544\n2545\n2546\n2547\n2548\n2549\n2550\n2551\n2552\n2553\n2554\n2555\n2556\n2557\n2558\n2559\n2560\n2561\n2562\n2563\n2564\n2565\n2566\n2567\n2568\n2569\n2570\n2571\n2572\n2573\n2574\n2575\n2576\n2577\n2578\n2579\n2580\n2581\n2582\n2583\n2584\n2585\n2586\n2587\n2588\n2589\n2590\n2591\n2592\n2593\n2594\n2595\n2596\n2597\n2598\n2599\n2600\n2601\n2602\n2603\n2604\n2605\n2606\n2607\n2608\n2609\n2610\n2611\n2612\n2613\n2614\n2615\n2616\n2617\n2618\n2619\n2620\n2621\n2622\n2623\n2624\n2625\n2626\n2627\n2628\n2629\n2630\n2631\n2632\n2633\n2634\n2635\n2636\n2637\n2638\n2639\n2640\n2641\n2642\n2643\n2644\n2645\n2646\n2647\n2648\n2649\n2650\n2651\n2652\n2653\n2654\n2655\n2656\n2657\n2658\n2659\n2660\n2661\n2662\n2663\n2664\n2665\n2666\n2667\n2668\n2669\n2670\n2671\n2672\n2673\n2674\n2675\n2676\n2677\n2678\n2679\n2680\n2681\n2682\n2683\n2684\n2685\n2686\n2687\n2688\n2689\n2690\n2691\n2692\n2693\n2694\n2695\n2696\n2697\n2698\n2699\n2700\n2701\n2702\n2703\n2704\n2705\n2706\n2707\n2708\n2709\n2710\n2711\n2712\n2713\n2714\n2715\n2716\n2717\n2718\n2719\n2720\n2721\n2722\n2723\n2724\n2725\n2726\n2727\n2728\n2729\n2730\n2731\n2732\n2733\n2734\n2735\n2736\n2737\n2738\n2739\n2740\n2741\n2742\n2743\n2744\n2745\n2746\n2747\n2748\n2749\n2750\n2751\n2752\n2753\n2754\n2755\n2756\n2757\n2758\n2759\n2760\n2761\n2762\n2763\n2764\n2765\n2766\n2767\n2768\n2769\n2770\n2771\n2772\n2773\n2774\n2775\n2776\n2777\n2778\n2779\n2780\n2781\n2782\n2783\n2784\n2785\n2786\n2787\n2788\n2789\n2790\n2791\n2792\n2793\n2794\n2795\n2796\n2797\n2798\n2799\n2800\n2801\n2802\n2803\n2804\n2805\n2806\n2807\n2808\n2809\n2810\n2811\n2812\n2813\n2814\n2815\n2816\n2817\n2818\n2819\n2820\n2821\n2822\n2823\n2824\n2825\n2826\n2827\n2828\n2829\n2830\n2831\n2832\n2833\n2834\n2835\n2836\n2837\n2838\n2839\n2840\n2841\n2842\n2843\n2844\n2845\n2846\n2847\n2848\n2849\n2850\n2851\n2852\n2853\n2854\n2855\n2856\n2857\n2858\n2859\n2860\n2861\n2862\n2863\n2864\n2865\n2866\n2867\n2868\n2869\n2870\n2871\n2872\n2873\n2874\n2875\n2876\n2877\n2878\n2879\n2880\n2881\n2882\n2883\n2884\n2885\n2886\n2887\n2888\n2889\n2890\n2891\n2892\n2893\n2894\n2895\n2896\n2897\n2898\n2899\n2900\n2901\n2902\n2903\n2904\n2905\n2906\n2907\n2908\n2909\n2910\n2911\n2912\n2913\n2914\n2915\n2916\n2917\n2918\n2919\n2920\n2921\n2922\n2923\n2924\n2925\n2926\n2927\n2928\n2929\n2930\n2931\n2932\n2933\n2934\n2935\n2936\n2937\n2938\n2939\n2940\n2941\n2942\n2943\n2944\n2945\n2946\n2947\n2948\n2949\n2950\n2951\n2952\n2953\n2954\n2955\n2956\n2957\n2958\n2959\n2960\n2961\n2962\n2963\n2964\n2965\n2966\n2967\n2968\n2969\n2970\n2971\n2972\n2973\n2974\n2975\n2976\n2977\n2978\n2979\n2980\n2981\n2982\n2983\n2984\n2985\n2986\n2987\n2988\n2989\n2990\n2991\n2992\n2993\n2994\n2995\n2996\n2997\n2998\n2999\n3000\n3001\n3002\n3003\n3004\n3005\n3006\n3007\n3008\n3009\n3010\n3011\n3012\n3013\n3014\n3015\n3016\n3017\n3018\n3019\n3020\n3021\n3022\n3023\n3024\n3025\n3026\n3027\n3028\n3029\n3030\n3031\n3032\n3033\n3034\n3035\n3036\n3037\n3038\n3039\n3040\n3041\n3042\n3043\n3044\n3045\n3046\n3047\n3048\n3049\n3050\n3051\n3052\n3053\n3054\n3055\n3056\n3057\n3058\n3059\n3060\n3061\n3062\n3063\n3064\n3065\n3066\n3067\n3068\n3069\n3070\n3071\n3072\n3073\n3074\n3075\n3076\n3077\n3078\n3079\n3080\n3081\n3082\n3083\n3084\n3085\n3086\n3087\n3088\n3089\n3090\n3091\n3092\n3093\n3094\n3095\n3096\n3097\n3098\n3099\n3100\n3101\n3102\n3103\n3104\n3105\n3106\n3107\n3108\n3109\n3110\n3111\n3112\n3113\n3114\n3115\n3116\n3117\n3118\n3119\n3120\n3121\n3122\n3123\n3124\n3125\n3126\n3127\n3128\n3129\n3130\n3131\n3132\n3133\n3134\n3135\n3136\n3137\n3138\n3139\n3140\n3141\n3142\n3143\n3144\n3145\n3146\n3147\n3148\n3149\n3150\n3151\n3152\n3153\n3154\n3155\n3156\n3157\n3158\n3159\n3160\n3161\n3162\n3163\n3164\n3165\n3166\n3167\n3168\n3169\n3170\n3171\n3172\n3173\n3174\n3175\n3176\n3177\n3178\n3179\n3180\n3181\n3182\n3183\n3184\n3185\n3186\n3187\n3188\n3189\n3190\n3191\n3192\n3193\n3194\n3195\n3196\n3197\n3198\n3199\n3200\n3201\n3202\n3203\n3204\n3205\n3206\n3207\n3208\n3209\n3210\n3211\n3212\n3213\n3214\n3215\n3216\n3217\n3218\n3219\n3220\n3221\n3222\n3223\n3224\n3225\n3226\n3227\n3228\n3229\n3230\n3231\n3232\n3233\n3234\n3235\n3236\n3237\n3238\n3239\n3240\n3241\n3242\n3243\n3244\n3245\n3246\n3247\n3248\n3249\n3250\n3251\n3252\n3253\n3254\n3255\n3256\n3257\n3258\n3259\n3260\n3261\n3262\n3263\n3264\n3265\n3266\n3267\n3268\n3269\n3270\n3271\n3272\n3273\n3274\n3275\n3276\n3277\n3278\n3279\n3280\n3281\n3282\n3283\n3284\n3285\n3286\n3287\n3288\n3289\n3290\n3291\n3292\n3293\n3294\n3295\n3296\n3297\n3298\n3299\n3300\n3301\n3302\n3303\n3304\n3305\n3306\n3307\n3308\n3309\n3310\n3311\n3312\n3313\n3314\n3315\n3316\n3317\n3318\n3319\n3320\n3321\n3322\n3323\n3324\n3325\n3326\n3327\n3328\n3329\n3330\n3331\n3332\n3333\n3334\n3335\n3336\n3337\n3338\n3339\n3340\n3341\n3342\n3343\n3344\n3345\n3346\n3347\n3348\n3349\n3350\n3351\n3352\n3353\n3354\n3355\n3356\n3357\n3358\n3359\n3360\n3361\n3362\n3363\n3364\n3365\n3366\n3367\n3368\n3369\n3370\n3371\n3372\n3373\n3374\n3375\n3376\n3377\n3378\n3379\n3380\n3381\n3382\n3383\n3384\n3385\n3386\n3387\n3388\n3389\n3390\n3391\n3392\n3393\n3394\n3395\n3396\n3397\n3398\n3399\n3400\n3401\n3402\n3403\n3404\n3405\n3406\n3407\n3408\n3409\n3410\n3411\n3412\n3413\n3414\n3415\n3416\n3417\n3418\n3419\n3420\n3421\n3422\n3423\n3424\n3425\n3426\n3427\n3428\n3429\n3430\n3431\n3432\n3433\n3434\n3435\n3436\n3437\n3438\n3439\n3440\n3441\n3442\n3443\n3444\n3445\n3446\n3447\n3448\n3449\n3450\n3451\n3452\n3453\n3454\n3455\n3456\n3457\n3458\n3459\n3460\n3461\n3462\n3463\n3464\n3465\n3466\n3467\n3468\n3469\n3470\n3471\n3472\n3473\n3474\n3475\n3476\n3477\n3478\n3479\n3480\n3481\n3482\n3483\n3484\n3485\n3486\n3487\n3488\n3489\n3490\n3491\n3492\n3493\n3494\n3495\n3496\n3497\n3498\n3499\n3500\n3501\n3502\n3503\n3504\n3505\n3506\n3507\n3508\n3509\n3510\n3511\n3512\n3513\n3514\n3515\n3516\n3517\n3518\n3519\n3520\n3521\n3522\n3523\n3524\n3525\n3526\n3527\n3528\n3529\n3530\n3531\n3532\n3533\n3534\n3535\n3536\n3537\n3538\n3539\n3540\n3541\n3542\n3543\n3544\n3545\n3546\n3547\n3548\n3549\n3550\n3551\n3552\n3553\n3554\n3555\n3556\n3557\n3558\n3559\n3560\n3561\n3562\n3563\n3564\n3565\n3566\n3567\n3568\n3569\n3570\n3571\n3572\n3573\n3574\n3575\n3576\n3577\n3578\n3579\n3580\n3581\n3582\n3583\n3584\n3585\n3586\n3587\n3588\n3589\n3590\n3591\n3592\n3593\n3594\n3595\n3596\n3597\n3598\n3599\n3600\n3601\n3602\n3603\n3604\n3605\n3606\n3607\n3608\n3609\n3610\n3611\n3612\n3613\n3614\n3615\n3616\n3617\n3618\n3619\n3620\n3621\n3622\n3623\n3624\n3625\n3626\n3627\n3628\n3629\n3630\n3631\n3632\n3633\n3634\n3635\n3636\n3637\n3638\n3639\n3640\n3641\n3642\n3643\n3644\n3645\n3646\n3647\n3648\n3649\n3650\n3651\n3652\n3653\n3654\n3655\n3656\n3657\n3658\n3659\n3660\n3661\n3662\n3663\n3664\n3665\n3666\n3667\n3668\n3669\n3670\n3671\n3672\n3673\n3674\n3675\n3676\n3677\n3678\n3679\n3680\n3681\n3682\n3683\n3684\n3685\n3686\n3687\n3688\n3689\n3690\n3691\n3692\n3693\n3694\n3695\n3696\n3697\n3698\n3699\n3700\n3701\n3702\n3703\n3704\n3705\n3706\n3707\n3708\n3709\n3710\n3711\n3712\n3713\n3714\n3715\n3716\n3717\n3718\n3719\n3720\n3721\n3722\n3723\n3724\n3725\n3726\n3727\n3728\n3729\n3730\n3731\n3732\n3733\n3734\n3735\n3736\n3737\n3738\n3739\n3740\n3741\n3742\n3743\n3744\n3745\n3746\n3747\n3748\n3749\n3750\n3751\n3752\n3753\n3754\n3755\n3756\n3757\n3758\n3759\n3760\n3761\n3762\n3763\n3764\n3765\n3766\n3767\n3768\n3769\n3770\n3771\n3772\n3773\n3774\n3775\n3776\n3777\n3778\n3779\n3780\n3781\n3782\n3783\n3784\n3785\n3786\n3787\n3788\n3789\n3790\n3791\n3792\n3793\n3794\n3795\n3796\n3797\n3798\n3799\n3800\n3801\n3802\n3803\n3804\n3805\n3806\n3807\n3808\n3809\n3810\n3811\n3812\n3813\n3814\n3815\n3816\n3817\n3818\n3819\n3820\n3821\n3822\n3823\n3824\n3825\n3826\n3827\n3828\n3829\n3830\n3831\n3832\n3833\n3834\n3835\n3836\n3837\n3838\n3839\n3840\n3841\n3842\n3843\n3844\n3845\n3846\n3847\n3848\n3849\n3850\n3851\n3852\n3853\n3854\n3855\n3856\n3857\n3858\n3859\n3860\n3861\n3862\n3863\n3864\n3865\n3866\n3867\n3868\n3869\n3870\n3871\n3872\n3873\n3874\n3875\n3876\n3877\n3878\n3879\n3880\n3881\n3882\n3883\n3884\n3885\n3886\n3887\n3888\n3889\n3890\n3891\n3892\n3893\n3894\n3895\n3896\n3897\n3898\n3899\n3900\n3901\n3902\n3903\n3904\n3905\n3906\n3907\n3908\n3909\n3910\n3911\n3912\n3913\n3914\n3915\n3916\n3917\n3918\n3919\n3920\n3921\n3922\n3923\n3924\n3925\n3926\n3927\n3928\n3929\n3930\n3931\n3932\n3933\n3934\n3935\n3936\n3937\n3938\n3939\n3940\n3941\n3942\n3943\n3944\n3945\n3946\n3947\n3948\n3949\n3950\n3951\n3952\n3953\n3954\n3955\n3956\n3957\n3958\n3959\n3960\n3961\n3962\n3963\n3964\n3965\n3966\n3967\n3968\n3969\n3970\n3971\n3972\n3973\n3974\n3975\n3976\n3977\n3978\n3979\n3980\n3981\n3982\n3983\n3984\n3985\n3986\n3987\n3988\n3989\n3990\n3991\n3992\n3993\n3994\n3995\n3996\n3997\n3998\n3999\n4000\n4001\n4002\n4003\n4004\n4005\n4006\n4007\n4008\n4009\n4010\n4011\n4012\n4013\n4014\n4015\n4016\n4017\n4018\n4019\n4020\n4021\n4022\n4023\n4024\n4025\n4026\n4027\n4028\n4029\n4030\n4031\n4032\n4033\n4034\n4035\n4036\n4037\n4038\n4039\n4040\n4041\n4042\n4043\n4044\n4045\n4046\n4047\n4048\n4049\n4050\n4051\n4052\n4053\n4054\n4055\n4056\n4057\n4058\n4059\n4060\n4061\n4062\n4063\n4064\n4065\n4066\n4067\n4068\n4069\n4070\n4071\n4072\n4073\n4074\n4075\n4076\n4077\n4078\n4079\n4080\n4081\n4082\n4083\n4084\n4085\n4086\n4087\n4088\n4089\n4090\n4091\n4092\n4093\n4094\n4095\n4096\n4097\n4098\n4099\n4100\n4101\n4102\n4103\n4104\n4105\n4106\n4107\n4108\n4109\n4110\n4111\n4112\n4113\n4114\n4115\n4116\n4117\n4118\n4119\n4120\n4121\n4122\n4123\n4124\n4125\n4126\n4127\n4128\n4129\n4130\n4131\n4132\n4133\n4134\n4135\n4136\n4137\n4138\n4139\n4140\n4141\n4142\n4143\n4144\n4145\n4146\n4147\n4148\n4149\n4150\n4151\n4152\n4153\n4154\n4155\n4156\n4157\n4158\n4159\n4160\n4161\n4162\n4163\n4164\n4165\n4166\n4167\n4168\n4169\n4170\n4171\n4172\n4173\n4174\n4175\n4176\n4177\n4178\n4179\n4180\n4181\n4182\n4183\n4184\n4185\n4186\n4187\n4188\n4189\n4190\n4191\n4192\n4193\n4194\n4195\n4196\n4197\n4198\n4199\n4200\n4201\n4202\n4203\n4204\n4205\n4206\n4207\n4208\n4209\n4210\n4211\n4212\n4213\n4214\n4215\n4216\n4217\n4218\n4219\n4220\n4221\n4222\n4223\n4224\n4225\n4226\n4227\n4228\n4229\n4230\n4231\n4232\n4233\n4234\n4235\n4236\n4237\n4238\n4239\n4240\n4241\n4242\n4243\n4244\n4245\n4246\n4247\n4248\n4249\n4250\n4251\n4252\n4253\n4254\n4255\n4256\n4257\n4258\n4259\n4260\n4261\n4262\n4263\n4264\n4265\n4266\n4267\n4268\n4269\n4270\n4271\n4272\n4273\n4274\n4275\n4276\n4277\n4278\n4279\n4280\n4281\n4282\n4283\n4284\n4285\n4286\n4287\n4288\n4289\n4290\n4291\n4292\n4293\n4294\n4295\n4296\n4297\n4298\n4299\n4300\n4301\n4302\n4303\n4304\n4305\n4306\n4307\n4308\n4309\n4310\n4311\n4312\n4313\n4314\n4315\n4316\n4317\n4318\n4319\n4320\n4321\n4322\n4323\n4324\n4325\n4326\n4327\n4328\n4329\n4330\n4331\n4332\n4333\n4334\n4335\n4336\n4337\n4338\n4339\n4340\n4341\n4342\n4343\n4344\n4345\n4346\n4347\n4348\n4349\n4350\n4351\n4352\n4353\n4354\n4355\n4356\n4357\n4358\n4359\n4360\n4361\n4362\n4363\n4364\n4365\n4366\n4367\n4368\n4369\n4370\n4371\n4372\n4373\n4374\n4375\n4376\n4377\n4378\n4379\n4380\n4381\n4382\n4383\n4384\n4385\n4386\n4387\n4388\n4389\n4390\n4391\n4392\n4393\n4394\n4395\n4396\n4397\n4398\n4399\n4400\n4401\n4402\n4403\n4404\n4405\n4406\n4407\n4408\n4409\n4410\n4411\n4412\n4413\n4414\n4415\n4416\n4417\n4418\n4419\n4420\n4421\n4422\n4423\n4424\n4425\n4426\n4427\n4428\n4429\n4430\n4431\n4432\n4433\n4434\n4435\n4436\n4437\n4438\n4439\n4440\n4441\n4442\n4443\n4444\n4445\n4446\n4447\n4448\n4449\n4450\n4451\n4452\n4453\n4454\n4455\n4456\n4457\n4458\n4459\n4460\n4461\n4462\n4463\n4464\n4465\n4466\n4467\n4468\n4469\n4470\n4471\n4472\n4473\n4474\n4475\n4476\n4477\n4478\n4479\n4480\n4481\n4482\n4483\n4484\n4485\n4486\n4487\n4488\n4489\n4490\n4491\n4492\n4493\n4494\n4495\n4496\n4497\n4498\n4499\n4500\n4501\n4502\n4503\n4504\n4505\n4506\n4507\n4508\n4509\n4510\n4511\n4512\n4513\n4514\n4515\n4516\n4517\n4518\n4519\n4520\n4521\n4522\n4523\n4524\n4525\n4526\n4527\n4528\n4529\n4530\n4531\n4532\n4533\n4534\n4535\n4536\n4537\n4538\n4539\n4540\n4541\n4542\n4543\n4544\n4545\n4546\n4547\n4548\n4549\n4550\n4551\n4552\n4553\n4554\n4555\n4556\n4557\n4558\n4559\n4560\n4561\n4562\n4563\n4564\n4565\n4566\n4567\n4568\n4569\n4570\n4571\n4572\n4573\n4574\n4575\n4576\n4577\n4578\n4579\n4580\n4581\n4582\n4583\n4584\n4585\n4586\n4587\n4588\n4589\n4590\n4591\n4592\n4593\n4594\n4595\n4596\n4597\n4598\n4599\n4600\n4601\n4602\n4603\n4604\n4605\n4606\n4607\n4608\n4609\n4610\n4611\n4612\n4613\n4614\n4615\n4616\n4617\n4618\n4619\n4620\n4621\n4622\n4623\n4624\n4625\n4626\n4627\n4628\n4629\n4630\n4631\n4632\n4633\n4634\n4635\n4636\n4637\n4638\n4639\n4640\n4641\n4642\n4643\n4644\n4645\n4646\n4647\n4648\n4649\n4650\n4651\n4652\n4653\n4654\n4655\n4656\n4657\n4658\n4659\n4660\n4661\n4662\n4663\n4664\n4665\n4666\n4667\n4668\n4669\n4670\n4671\n4672\n4673\n4674\n4675\n4676\n4677\n4678\n4679\n4680\n4681\n4682\n4683\n4684\n4685\n4686\n4687\n4688\n4689\n4690\n4691\n4692\n4693\n4694\n4695\n4696\n4697\n4698\n4699\n4700\n4701\n4702\n4703\n4704\n4705\n4706\n4707\n4708\n4709\n4710\n4711\n4712\n4713\n4714\n4715\n4716\n4717\n4718\n4719\n4720\n4721\n4722\n4723\n4724\n4725\n4726\n4727\n4728\n4729\n4730\n4731\n4732\n4733\n4734\n4735\n4736\n4737\n4738\n4739\n4740\n4741\n4742\n4743\n4744\n4745\n4746\n4747\n4748\n4749\n4750\n4751\n4752\n4753\n4754\n4755\n4756\n4757\n4758\n4759\n4760\n4761\n4762\n4763\n4764\n4765\n4766\n4767\n4768\n4769\n4770\n4771\n4772\n4773\n4774\n4775\n4776\n4777\n4778\n4779\n4780\n4781\n4782\n4783\n4784\n4785\n4786\n4787\n4788\n4789\n4790\n4791\n4792\n4793\n4794\n4795\n4796\n4797\n4798\n4799\n4800\n4801\n4802\n4803\n4804\n4805\n4806\n4807\n4808\n4809\n4810\n4811\n4812\n4813\n4814\n4815\n4816\n4817\n4818\n4819\n4820\n4821\n4822\n4823\n4824\n4825\n4826\n4827\n4828\n4829\n4830\n4831\n4832\n4833\n4834\n4835\n4836\n4837\n4838\n4839\n4840\n4841\n4842\n4843\n4844\n4845\n4846\n4847\n4848\n4849\n4850\n4851\n4852\n4853\n4854\n4855\n4856\n4857\n4858\n4859\n4860\n4861\n4862\n4863\n4864\n4865\n4866\n4867\n4868\n4869\n4870\n4871\n4872\n4873\n4874\n4875\n4876\n4877\n4878\n4879\n4880\n4881\n4882\n4883\n4884\n4885\n4886\n4887\n4888\n4889\n4890\n4891\n4892\n4893\n4894\n4895\n4896\n4897\n4898\n4899\n4900\n4901\n4902\n4903\n4904\n4905\n4906\n4907\n4908\n4909\n4910\n4911\n4912\n4913\n4914\n4915\n4916\n4917\n4918\n4919\n4920\n4921\n4922\n4923\n4924\n4925\n4926\n4927\n4928\n4929\n4930\n4931\n4932\n4933\n4934\n4935\n4936\n4937\n4938\n4939\n4940\n4941\n4942\n4943\n4944\n4945\n4946\n4947\n4948\n4949\n4950\n4951\n4952\n4953\n4954\n4955\n4956\n4957\n4958\n4959\n4960\n4961\n4962\n4963\n4964\n4965\n4966\n4967\n4968\n4969\n4970\n4971\n4972\n4973\n4974\n4975\n4976\n4977\n4978\n4979\n4980\n4981\n4982\n4983\n4984\n4985\n4986\n4987\n4988\n4989\n4990\n4991\n4992\n4993\n4994\n4995\n4996\n4997\n4998\n4999\n%(5000)\n5001\n5002\n5003\n5004\n5005\n5006\n5007\n5008\n5009\n5010\n5011\n5012\n5013\n5014\n5015\n5016\n5017\n5018\n5019\n5020\n5021\n5022\n5023\n5024\n5025\n5026\n5027\n5028\n5029\n5030\n5031\n5032\n5033\n5034\n5035\n5036\n5037\n5038\n5039\n5040\n5041\n5042\n5043\n5044\n5045\n5046\n5047\n5048\n5049\n5050\n5051\n5052\n5053\n5054\n5055\n5056\n5057\n5058\n5059\n5060\n5061\n5062\n5063\n5064\n5065\n5066\n5067\n5068\n5069\n5070\n5071\n5072\n5073\n5074\n5075\n5076\n5077\n5078\n5079\n5080\n5081\n5082\n5083\n5084\n5085\n5086\n5087\n5088\n5089\n5090\n5091\n5092\n5093\n5094\n5095\n5096\n5097\n5098\n5099\n5100\n5101\n5102\n5103\n5104\n5105\n5106\n5107\n5108\n5109\n5110\n5111\n5112\n5113\n5114\n5115\n5116\n5117\n5118\n5119\n5120\n5121\n5122\n5123\n5124\n5125\n5126\n5127\n5128\n5129\n5130\n5131\n5132\n5133\n5134\n5135\n5136\n5137\n5138\n5139\n5140\n5141\n5142\n5143\n5144\n5145\n5146\n5147\n5148\n5149\n5150\n5151\n5152\n5153\n5154\n5155\n5156\n5157\n5158\n5159\n5160\n5161\n5162\n5163\n5164\n5165\n5166\n5167\n5168\n5169\n5170\n5171\n5172\n5173\n5174\n5175\n5176\n5177\n5178\n5179\n5180\n5181\n5182\n5183\n5184\n5185\n5186\n5187\n5188\n5189\n5190\n5191\n5192\n5193\n5194\n5195\n5196\n5197\n5198\n5199\n5200\n5201\n5202\n5203\n5204\n5205\n5206\n5207\n5208\n5209\n5210\n5211\n5212\n5213\n5214\n5215\n5216\n5217\n5218\n5219\n5220\n5221\n5222\n5223\n5224\n5225\n5226\n5227\n5228\n5229\n5230\n5231\n5232\n5233\n5234\n5235\n5236\n5237\n5238\n5239\n5240\n5241\n5242\n5243\n5244\n5245\n5246\n5247\n5248\n5249\n5250\n5251\n5252\n5253\n5254\n5255\n5256\n5257\n5258\n5259\n5260\n5261\n5262\n5263\n5264\n5265\n5266\n5267\n5268\n5269\n5270\n5271\n5272\n5273\n5274\n5275\n5276\n5277\n5278\n5279\n5280\n5281\n5282\n5283\n5284\n5285\n5286\n5287\n5288\n5289\n5290\n5291\n5292\n5293\n5294\n5295\n5296\n5297\n5298\n5299\n5300\n5301\n5302\n5303\n5304\n5305\n5306\n5307\n5308\n5309\n5310\n5311\n5312\n5313\n5314\n5315\n5316\n5317\n5318\n5319\n5320\n5321\n5322\n5323\n5324\n5325\n5326\n5327\n5328\n5329\n5330\n5331\n5332\n5333\n5334\n5335\n5336\n5337\n5338\n5339\n5340\n5341\n5342\n5343\n5344\n5345\n5346\n5347\n5348\n5349\n5350\n5351\n5352\n5353\n5354\n5355\n5356\n5357\n5358\n5359\n5360\n5361\n5362\n5363\n5364\n5365\n5366\n5367\n5368\n5369\n5370\n5371\n5372\n5373\n5374\n5375\n5376\n5377\n5378\n5379\n5380\n5381\n5382\n5383\n5384\n5385\n5386\n5387\n5388\n5389\n5390\n5391\n5392\n5393\n5394\n5395\n5396\n5397\n5398\n5399\n5400\n5401\n5402\n5403\n5404\n5405\n5406\n5407\n5408\n5409\n5410\n5411\n5412\n5413\n5414\n5415\n5416\n5417\n5418\n5419\n5420\n5421\n5422\n5423\n5424\n5425\n5426\n5427\n5428\n5429\n5430\n5431\n5432\n5433\n5434\n5435\n5436\n5437\n5438\n5439\n5440\n5441\n5442\n5443\n5444\n5445\n5446\n5447\n5448\n5449\n5450\n5451\n5452\n5453\n5454\n5455\n5456\n5457\n5458\n5459\n5460\n5461\n5462\n5463\n5464\n5465\n5466\n5467\n5468\n5469\n5470\n5471\n5472\n5473\n5474\n5475\n5476\n5477\n5478\n5479\n5480\n5481\n5482\n5483\n5484\n5485\n5486\n5487\n5488\n5489\n5490\n5491\n5492\n5493\n5494\n5495\n5496\n5497\n5498\n5499\n5500\n5501\n5502\n5503\n5504\n5505\n5506\n5507\n5508\n5509\n5510\n5511\n5512\n5513\n5514\n5515\n5516\n5517\n5518\n5519\n5520\n5521\n5522\n5523\n5524\n5525\n5526\n5527\n5528\n5529\n5530\n5531\n5532\n5533\n5534\n5535\n5536\n5537\n5538\n5539\n5540\n5541\n5542\n5543\n5544\n5545\n5546\n5547\n5548\n5549\n5550\n5551\n5552\n5553\n5554\n5555\n5556\n5557\n5558\n5559\n5560\n5561\n5562\n5563\n5564\n5565\n5566\n5567\n5568\n5569\n5570\n5571\n5572\n5573\n5574\n5575\n5576\n5577\n5578\n5579\n5580\n5581\n5582\n5583\n5584\n5585\n5586\n5587\n5588\n5589\n5590\n5591\n5592\n5593\n5594\n5595\n5596\n5597\n5598\n5599\n5600\n5601\n5602\n5603\n5604\n5605\n5606\n5607\n5608\n5609\n5610\n5611\n5612\n5613\n5614\n5615\n5616\n5617\n5618\n5619\n5620\n5621\n5622\n5623\n5624\n5625\n5626\n5627\n5628\n5629\n5630\n5631\n5632\n5633\n5634\n5635\n5636\n5637\n5638\n5639\n5640\n5641\n5642\n5643\n5644\n5645\n5646\n5647\n5648\n5649\n5650\n5651\n5652\n5653\n5654\n5655\n5656\n5657\n5658\n5659\n5660\n5661\n5662\n5663\n5664\n5665\n5666\n5667\n5668\n5669\n5670\n5671\n5672\n5673\n5674\n5675\n5676\n5677\n5678\n5679\n5680\n5681\n5682\n5683\n5684\n5685\n5686\n5687\n5688\n5689\n5690\n5691\n5692\n5693\n5694\n5695\n5696\n5697\n5698\n5699\n5700\n5701\n5702\n5703\n5704\n5705\n5706\n5707\n5708\n5709\n5710\n5711\n5712\n5713\n5714\n5715\n5716\n5717\n5718\n5719\n5720\n5721\n5722\n5723\n5724\n5725\n5726\n5727\n5728\n5729\n5730\n5731\n5732\n5733\n5734\n5735\n5736\n5737\n5738\n5739\n5740\n5741\n5742\n5743\n5744\n5745\n5746\n5747\n5748\n5749\n5750\n5751\n5752\n5753\n5754\n5755\n5756\n5757\n5758\n5759\n5760\n5761\n5762\n5763\n5764\n5765\n5766\n5767\n5768\n5769\n5770\n5771\n5772\n5773\n5774\n5775\n5776\n5777\n5778\n5779\n5780\n5781\n5782\n5783\n5784\n5785\n5786\n5787\n5788\n5789\n5790\n5791\n5792\n5793\n5794\n5795\n5796\n5797\n5798\n5799\n5800\n5801\n5802\n5803\n5804\n5805\n5806\n5807\n5808\n5809\n5810\n5811\n5812\n5813\n5814\n5815\n5816\n5817\n5818\n5819\n5820\n5821\n5822\n5823\n5824\n5825\n5826\n5827\n5828\n5829\n5830\n5831\n5832\n5833\n5834\n5835\n5836\n5837\n5838\n5839\n5840\n5841\n5842\n5843\n5844\n5845\n5846\n5847\n5848\n5849\n5850\n5851\n5852\n5853\n5854\n5855\n5856\n5857\n5858\n5859\n5860\n5861\n5862\n5863\n5864\n5865\n5866\n5867\n5868\n5869\n5870\n5871\n5872\n5873\n5874\n5875\n5876\n5877\n5878\n5879\n5880\n5881\n5882\n5883\n5884\n5885\n5886\n5887\n5888\n5889\n5890\n5891\n5892\n5893\n5894\n5895\n5896\n5897\n5898\n5899\n5900\n5901\n5902\n5903\n5904\n5905\n5906\n5907\n5908\n5909\n5910\n5911\n5912\n5913\n5914\n5915\n5916\n5917\n5918\n5919\n5920\n5921\n5922\n5923\n5924\n5925\n5926\n5927\n5928\n5929\n5930\n5931\n5932\n5933\n5934\n5935\n5936\n5937\n5938\n5939\n5940\n5941\n5942\n5943\n5944\n5945\n5946\n5947\n5948\n5949\n5950\n5951\n5952\n5953\n5954\n5955\n5956\n5957\n5958\n5959\n5960\n5961\n5962\n5963\n5964\n5965\n5966\n5967\n5968\n5969\n5970\n5971\n5972\n5973\n5974\n5975\n5976\n5977\n5978\n5979\n5980\n5981\n5982\n5983\n5984\n5985\n5986\n5987\n5988\n5989\n5990\n5991\n5992\n5993\n5994\n5995\n5996\n5997\n5998\n5999\n6000\n6001\n6002\n6003\n6004\n6005\n6006\n6007\n6008\n6009\n6010\n6011\n6012\n6013\n6014\n6015\n6016\n6017\n6018\n6019\n6020\n6021\n6022\n6023\n6024\n6025\n6026\n6027\n6028\n6029\n6030\n6031\n6032\n6033\n6034\n6035\n6036\n6037\n6038\n6039\n6040\n6041\n6042\n6043\n6044\n6045\n6046\n6047\n6048\n6049\n6050\n6051\n6052\n6053\n6054\n6055\n6056\n6057\n6058\n6059\n6060\n6061\n6062\n6063\n6064\n6065\n6066\n6067\n6068\n6069\n6070\n6071\n6072\n6073\n6074\n6075\n6076\n6077\n6078\n6079\n6080\n6081\n6082\n6083\n6084\n6085\n6086\n6087\n6088\n6089\n6090\n6091\n6092\n6093\n6094\n6095\n6096\n6097\n6098\n6099\n6100\n6101\n6102\n6103\n6104\n6105\n6106\n6107\n6108\n6109\n6110\n6111\n6112\n6113\n6114\n6115\n6116\n6117\n6118\n6119\n6120\n6121\n6122\n6123\n6124\n6125\n6126\n6127\n6128\n6129\n6130\n6131\n6132\n6133\n6134\n6135\n6136\n6137\n6138\n6139\n6140\n6141\n6142\n6143\n6144\n6145\n6146\n6147\n6148\n6149\n6150\n6151\n6152\n6153\n6154\n6155\n6156\n6157\n6158\n6159\n6160\n6161\n6162\n6163\n6164\n6165\n6166\n6167\n6168\n6169\n6170\n6171\n6172\n6173\n6174\n6175\n6176\n6177\n6178\n6179\n6180\n6181\n6182\n6183\n6184\n6185\n6186\n6187\n6188\n6189\n6190\n6191\n6192\n6193\n6194\n6195\n6196\n6197\n6198\n6199\n6200\n6201\n6202\n6203\n6204\n6205\n6206\n6207\n6208\n6209\n6210\n6211\n6212\n6213\n6214\n6215\n6216\n6217\n6218\n6219\n6220\n6221\n6222\n6223\n6224\n6225\n6226\n6227\n6228\n6229\n6230\n6231\n6232\n6233\n6234\n6235\n6236\n6237\n6238\n6239\n6240\n6241\n6242\n6243\n6244\n6245\n6246\n6247\n6248\n6249\n6250\n6251\n6252\n6253\n6254\n6255\n6256\n6257\n6258\n6259\n6260\n6261\n6262\n6263\n6264\n6265\n6266\n6267\n6268\n6269\n6270\n6271\n6272\n6273\n6274\n6275\n6276\n6277\n6278\n6279\n6280\n6281\n6282\n6283\n6284\n6285\n6286\n6287\n6288\n6289\n6290\n6291\n6292\n6293\n6294\n6295\n6296\n6297\n6298\n6299\n6300\n6301\n6302\n6303\n6304\n6305\n6306\n6307\n6308\n6309\n6310\n6311\n6312\n6313\n6314\n6315\n6316\n6317\n6318\n6319\n6320\n6321\n6322\n6323\n6324\n6325\n6326\n6327\n6328\n6329\n6330\n6331\n6332\n6333\n6334\n6335\n6336\n6337\n6338\n6339\n6340\n6341\n6342\n6343\n6344\n6345\n6346\n6347\n6348\n6349\n6350\n6351\n6352\n6353\n6354\n6355\n6356\n6357\n6358\n6359\n6360\n6361\n6362\n6363\n6364\n6365\n6366\n6367\n6368\n6369\n6370\n6371\n6372\n6373\n6374\n6375\n6376\n6377\n6378\n6379\n6380\n6381\n6382\n6383\n6384\n6385\n6386\n6387\n6388\n6389\n6390\n6391\n6392\n6393\n6394\n6395\n6396\n6397\n6398\n6399\n6400\n6401\n6402\n6403\n6404\n6405\n6406\n6407\n6408\n6409\n6410\n6411\n6412\n6413\n6414\n6415\n6416\n6417\n6418\n6419\n6420\n6421\n6422\n6423\n6424\n6425\n6426\n6427\n6428\n6429\n6430\n6431\n6432\n6433\n6434\n6435\n6436\n6437\n6438\n6439\n6440\n6441\n6442\n6443\n6444\n6445\n6446\n6447\n6448\n6449\n6450\n6451\n6452\n6453\n6454\n6455\n6456\n6457\n6458\n6459\n6460\n6461\n6462\n6463\n6464\n6465\n6466\n6467\n6468\n6469\n6470\n6471\n6472\n6473\n6474\n6475\n6476\n6477\n6478\n6479\n6480\n6481\n6482\n6483\n6484\n6485\n6486\n6487\n6488\n6489\n6490\n6491\n6492\n6493\n6494\n6495\n6496\n6497\n6498\n6499\n6500\n6501\n6502\n6503\n6504\n6505\n6506\n6507\n6508\n6509\n6510\n6511\n6512\n6513\n6514\n6515\n6516\n6517\n6518\n6519\n6520\n6521\n6522\n6523\n6524\n6525\n6526\n6527\n6528\n6529\n6530\n6531\n6532\n6533\n6534\n6535\n6536\n6537\n6538\n6539\n6540\n6541\n6542\n6543\n6544\n6545\n6546\n6547\n6548\n6549\n6550\n6551\n6552\n6553\n6554\n6555\n6556\n6557\n6558\n6559\n6560\n6561\n6562\n6563\n6564\n6565\n6566\n6567\n6568\n6569\n6570\n6571\n6572\n6573\n6574\n6575\n6576\n6577\n6578\n6579\n6580\n6581\n6582\n6583\n6584\n6585\n6586\n6587\n6588\n6589\n6590\n6591\n6592\n6593\n6594\n6595\n6596\n6597\n6598\n6599\n6600\n6601\n6602\n6603\n6604\n6605\n6606\n6607\n6608\n6609\n6610\n6611\n6612\n6613\n6614\n6615\n6616\n6617\n6618\n6619\n6620\n6621\n6622\n6623\n6624\n6625\n6626\n6627\n6628\n6629\n6630\n6631\n6632\n6633\n6634\n6635\n6636\n6637\n6638\n6639\n6640\n6641\n6642\n6643\n6644\n6645\n6646\n6647\n6648\n6649\n6650\n6651\n6652\n6653\n6654\n6655\n6656\n6657\n6658\n6659\n6660\n6661\n6662\n6663\n6664\n6665\n6666\n6667\n6668\n6669\n6670\n6671\n6672\n6673\n6674\n6675\n6676\n6677\n6678\n6679\n6680\n6681\n6682\n6683\n6684\n6685\n6686\n6687\n6688\n6689\n6690\n6691\n6692\n6693\n6694\n6695\n6696\n6697\n6698\n6699\n6700\n6701\n6702\n6703\n6704\n6705\n6706\n6707\n6708\n6709\n6710\n6711\n6712\n6713\n6714\n6715\n6716\n6717\n6718\n6719\n6720\n6721\n6722\n6723\n6724\n6725\n6726\n6727\n6728\n6729\n6730\n6731\n6732\n6733\n6734\n6735\n6736\n6737\n6738\n6739\n6740\n6741\n6742\n6743\n6744\n6745\n6746\n6747\n6748\n6749\n6750\n6751\n6752\n6753\n6754\n6755\n6756\n6757\n6758\n6759\n6760\n6761\n6762\n6763\n6764\n6765\n6766\n6767\n6768\n6769\n6770\n6771\n6772\n6773\n6774\n6775\n6776\n6777\n6778\n6779\n6780\n6781\n6782\n6783\n6784\n6785\n6786\n6787\n6788\n6789\n6790\n6791\n6792\n6793\n6794\n6795\n6796\n6797\n6798\n6799\n6800\n6801\n6802\n6803\n6804\n6805\n6806\n6807\n6808\n6809\n6810\n6811\n6812\n6813\n6814\n6815\n6816\n6817\n6818\n6819\n6820\n6821\n6822\n6823\n6824\n6825\n6826\n6827\n6828\n6829\n6830\n6831\n6832\n6833\n6834\n6835\n6836\n6837\n6838\n6839\n6840\n6841\n6842\n6843\n6844\n6845\n6846\n6847\n6848\n6849\n6850\n6851\n6852\n6853\n6854\n6855\n6856\n6857\n6858\n6859\n6860\n6861\n6862\n6863\n6864\n6865\n6866\n6867\n6868\n6869\n6870\n6871\n6872\n6873\n6874\n6875\n6876\n6877\n6878\n6879\n6880\n6881\n6882\n6883\n6884\n6885\n6886\n6887\n6888\n6889\n6890\n6891\n6892\n6893\n6894\n6895\n6896\n6897\n6898\n6899\n6900\n6901\n6902\n6903\n6904\n6905\n6906\n6907\n6908\n6909\n6910\n6911\n6912\n6913\n6914\n6915\n6916\n6917\n6918\n6919\n6920\n6921\n6922\n6923\n6924\n6925\n6926\n6927\n6928\n6929\n6930\n6931\n6932\n6933\n6934\n6935\n6936\n6937\n6938\n6939\n6940\n6941\n6942\n6943\n6944\n6945\n6946\n6947\n6948\n6949\n6950\n6951\n6952\n6953\n6954\n6955\n6956\n6957\n6958\n6959\n6960\n6961\n6962\n6963\n6964\n6965\n6966\n6967\n6968\n6969\n6970\n6971\n6972\n6973\n6974\n6975\n6976\n6977\n6978\n6979\n6980\n6981\n6982\n6983\n6984\n6985\n6986\n6987\n6988\n6989\n6990\n6991\n6992\n6993\n6994\n6995\n6996\n6997\n6998\n6999\n7000\n7001\n7002\n7003\n7004\n7005\n7006\n7007\n7008\n7009\n7010\n7011\n7012\n7013\n7014\n7015\n7016\n7017\n7018\n7019\n7020\n7021\n7022\n7023\n7024\n7025\n7026\n7027\n7028\n7029\n7030\n7031\n7032\n7033\n7034\n7035\n7036\n7037\n7038\n7039\n7040\n7041\n7042\n7043\n7044\n7045\n7046\n7047\n7048\n7049\n7050\n7051\n7052\n7053\n7054\n7055\n7056\n7057\n7058\n7059\n7060\n7061\n7062\n7063\n7064\n7065\n7066\n7067\n7068\n7069\n7070\n7071\n7072\n7073\n7074\n7075\n7076\n7077\n7078\n7079\n7080\n7081\n7082\n7083\n7084\n7085\n7086\n7087\n7088\n7089\n7090\n7091\n7092\n7093\n7094\n7095\n7096\n7097\n7098\n7099\n7100\n7101\n7102\n7103\n7104\n7105\n7106\n7107\n7108\n7109\n7110\n7111\n7112\n7113\n7114\n7115\n7116\n7117\n7118\n7119\n7120\n7121\n7122\n7123\n7124\n7125\n7126\n7127\n7128\n7129\n7130\n7131\n7132\n7133\n7134\n7135\n7136\n7137\n7138\n7139\n7140\n7141\n7142\n7143\n7144\n7145\n7146\n7147\n7148\n7149\n7150\n7151\n7152\n7153\n7154\n7155\n7156\n7157\n7158\n7159\n7160\n7161\n7162\n7163\n7164\n7165\n7166\n7167\n7168\n7169\n7170\n7171\n7172\n7173\n7174\n7175\n7176\n7177\n7178\n7179\n7180\n7181\n7182\n7183\n7184\n7185\n7186\n7187\n7188\n7189\n7190\n7191\n7192\n7193\n7194\n7195\n7196\n7197\n7198\n7199\n7200\n7201\n7202\n7203\n7204\n7205\n7206\n7207\n7208\n7209\n7210\n7211\n7212\n7213\n7214\n7215\n7216\n7217\n7218\n7219\n7220\n7221\n7222\n7223\n7224\n7225\n7226\n7227\n7228\n7229\n7230\n7231\n7232\n7233\n7234\n7235\n7236\n7237\n7238\n7239\n7240\n7241\n7242\n7243\n7244\n7245\n7246\n7247\n7248\n7249\n7250\n7251\n7252\n7253\n7254\n7255\n7256\n7257\n7258\n7259\n7260\n7261\n7262\n7263\n7264\n7265\n7266\n7267\n7268\n7269\n7270\n7271\n7272\n7273\n7274\n7275\n7276\n7277\n7278\n7279\n7280\n7281\n7282\n7283\n7284\n7285\n7286\n7287\n7288\n7289\n7290\n7291\n7292\n7293\n7294\n7295\n7296\n7297\n7298\n7299\n7300\n7301\n7302\n7303\n7304\n7305\n7306\n7307\n7308\n7309\n7310\n7311\n7312\n7313\n7314\n7315\n7316\n7317\n7318\n7319\n7320\n7321\n7322\n7323\n7324\n7325\n7326\n7327\n7328\n7329\n7330\n7331\n7332\n7333\n7334\n7335\n7336\n7337\n7338\n7339\n7340\n7341\n7342\n7343\n7344\n7345\n7346\n7347\n7348\n7349\n7350\n7351\n7352\n7353\n7354\n7355\n7356\n7357\n7358\n7359\n7360\n7361\n7362\n7363\n7364\n7365\n7366\n7367\n7368\n7369\n7370\n7371\n7372\n7373\n7374\n7375\n7376\n7377\n7378\n7379\n7380\n7381\n7382\n7383\n7384\n7385\n7386\n7387\n7388\n7389\n7390\n7391\n7392\n7393\n7394\n7395\n7396\n7397\n7398\n7399\n7400\n7401\n7402\n7403\n7404\n7405\n7406\n7407\n7408\n7409\n7410\n7411\n7412\n7413\n7414\n7415\n7416\n7417\n7418\n7419\n7420\n7421\n7422\n7423\n7424\n7425\n7426\n7427\n7428\n7429\n7430\n7431\n7432\n7433\n7434\n7435\n7436\n7437\n7438\n7439\n7440\n7441\n7442\n7443\n7444\n7445\n7446\n7447\n7448\n7449\n7450\n7451\n7452\n7453\n7454\n7455\n7456\n7457\n7458\n7459\n7460\n7461\n7462\n7463\n7464\n7465\n7466\n7467\n7468\n7469\n7470\n7471\n7472\n7473\n7474\n7475\n7476\n7477\n7478\n7479\n7480\n7481\n7482\n7483\n7484\n7485\n7486\n7487\n7488\n7489\n7490\n7491\n7492\n7493\n7494\n7495\n7496\n7497\n7498\n7499\n7500\n7501\n7502\n7503\n7504\n7505\n7506\n7507\n7508\n7509\n7510\n7511\n7512\n7513\n7514\n7515\n7516\n7517\n7518\n7519\n7520\n7521\n7522\n7523\n7524\n7525\n7526\n7527\n7528\n7529\n7530\n7531\n7532\n7533\n7534\n7535\n7536\n7537\n7538\n7539\n7540\n7541\n7542\n7543\n7544\n7545\n7546\n7547\n7548\n7549\n7550\n7551\n7552\n7553\n7554\n7555\n7556\n7557\n7558\n7559\n7560\n7561\n7562\n7563\n7564\n7565\n7566\n7567\n7568\n7569\n7570\n7571\n7572\n7573\n7574\n7575\n7576\n7577\n7578\n7579\n7580\n7581\n7582\n7583\n7584\n7585\n7586\n7587\n7588\n7589\n7590\n7591\n7592\n7593\n7594\n7595\n7596\n7597\n7598\n7599\n7600\n7601\n7602\n7603\n7604\n7605\n7606\n7607\n7608\n7609\n7610\n7611\n7612\n7613\n7614\n7615\n7616\n7617\n7618\n7619\n7620\n7621\n7622\n7623\n7624\n7625\n7626\n7627\n7628\n7629\n7630\n7631\n7632\n7633\n7634\n7635\n7636\n7637\n7638\n7639\n7640\n7641\n7642\n7643\n7644\n7645\n7646\n7647\n7648\n7649\n7650\n7651\n7652\n7653\n7654\n7655\n7656\n7657\n7658\n7659\n7660\n7661\n7662\n7663\n7664\n7665\n7666\n7667\n7668\n7669\n7670\n7671\n7672\n7673\n7674\n7675\n7676\n7677\n7678\n7679\n7680\n7681\n7682\n7683\n7684\n7685\n7686\n7687\n7688\n7689\n7690\n7691\n7692\n7693\n7694\n7695\n7696\n7697\n7698\n7699\n7700\n7701\n7702\n7703\n7704\n7705\n7706\n7707\n7708\n7709\n7710\n7711\n7712\n7713\n7714\n7715\n7716\n7717\n7718\n7719\n7720\n7721\n7722\n7723\n7724\n7725\n7726\n7727\n7728\n7729\n7730\n7731\n7732\n7733\n7734\n7735\n7736\n7737\n7738\n7739\n7740\n7741\n7742\n7743\n7744\n7745\n7746\n7747\n7748\n7749\n7750\n7751\n7752\n7753\n7754\n7755\n7756\n7757\n7758\n7759\n7760\n7761\n7762\n7763\n7764\n7765\n7766\n7767\n7768\n7769\n7770\n7771\n7772\n7773\n7774\n7775\n7776\n7777\n7778\n7779\n7780\n7781\n7782\n7783\n7784\n7785\n7786\n7787\n7788\n7789\n7790\n7791\n7792\n7793\n7794\n7795\n7796\n7797\n7798\n7799\n7800\n7801\n7802\n7803\n7804\n7805\n7806\n7807\n7808\n7809\n7810\n7811\n7812\n7813\n7814\n7815\n7816\n7817\n7818\n7819\n7820\n7821\n7822\n7823\n7824\n7825\n7826\n7827\n7828\n7829\n7830\n7831\n7832\n7833\n7834\n7835\n7836\n7837\n7838\n7839\n7840\n7841\n7842\n7843\n7844\n7845\n7846\n7847\n7848\n7849\n7850\n7851\n7852\n7853\n7854\n7855\n7856\n7857\n7858\n7859\n7860\n7861\n7862\n7863\n7864\n7865\n7866\n7867\n7868\n7869\n7870\n7871\n7872\n7873\n7874\n7875\n7876\n7877\n7878\n7879\n7880\n7881\n7882\n7883\n7884\n7885\n7886\n7887\n7888\n7889\n7890\n7891\n7892\n7893\n7894\n7895\n7896\n7897\n7898\n7899\n7900\n7901\n7902\n7903\n7904\n7905\n7906\n7907\n7908\n7909\n7910\n7911\n7912\n7913\n7914\n7915\n7916\n7917\n7918\n7919\n7920\n7921\n7922\n7923\n7924\n7925\n7926\n7927\n7928\n7929\n7930\n7931\n7932\n7933\n7934\n7935\n7936\n7937\n7938\n7939\n7940\n7941\n7942\n7943\n7944\n7945\n7946\n7947\n7948\n7949\n7950\n7951\n7952\n7953\n7954\n7955\n7956\n7957\n7958\n7959\n7960\n7961\n7962\n7963\n7964\n7965\n7966\n7967\n7968\n7969\n7970\n7971\n7972\n7973\n7974\n7975\n7976\n7977\n7978\n7979\n7980\n7981\n7982\n7983\n7984\n7985\n7986\n7987\n7988\n7989\n7990\n7991\n7992\n7993\n7994\n7995\n7996\n7997\n7998\n7999\n8000\n8001\n8002\n8003\n8004\n8005\n8006\n8007\n8008\n8009\n8010\n8011\n8012\n8013\n8014\n8015\n8016\n8017\n8018\n8019\n8020\n8021\n8022\n8023\n8024\n8025\n8026\n8027\n8028\n8029\n8030\n8031\n8032\n8033\n8034\n8035\n8036\n8037\n8038\n8039\n8040\n8041\n8042\n8043\n8044\n8045\n8046\n8047\n8048\n8049\n8050\n8051\n8052\n8053\n8054\n8055\n8056\n8057\n8058\n8059\n8060\n8061\n8062\n8063\n8064\n8065\n8066\n8067\n8068\n8069\n8070\n8071\n8072\n8073\n8074\n8075\n8076\n8077\n8078\n8079\n8080\n8081\n8082\n8083\n8084\n8085\n8086\n8087\n8088\n8089\n8090\n8091\n8092\n8093\n8094\n8095\n8096\n8097\n8098\n8099\n8100\n8101\n8102\n8103\n8104\n8105\n8106\n8107\n8108\n8109\n8110\n8111\n8112\n8113\n8114\n8115\n8116\n8117\n8118\n8119\n8120\n8121\n8122\n8123\n8124\n8125\n8126\n8127\n8128\n8129\n8130\n8131\n8132\n8133\n8134\n8135\n8136\n8137\n8138\n8139\n8140\n8141\n8142\n8143\n8144\n8145\n8146\n8147\n8148\n8149\n8150\n8151\n8152\n8153\n8154\n8155\n8156\n8157\n8158\n8159\n8160\n8161\n8162\n8163\n8164\n8165\n8166\n8167\n8168\n8169\n8170\n8171\n8172\n8173\n8174\n8175\n8176\n8177\n8178\n8179\n8180\n8181\n8182\n8183\n8184\n8185\n8186\n8187\n8188\n8189\n8190\n8191\n8192\n8193\n8194\n8195\n8196\n8197\n8198\n8199\n8200\n8201\n8202\n8203\n8204\n8205\n8206\n8207\n8208\n8209\n8210\n8211\n8212\n8213\n8214\n8215\n8216\n8217\n8218\n8219\n8220\n8221\n8222\n8223\n8224\n8225\n8226\n8227\n8228\n8229\n8230\n8231\n8232\n8233\n8234\n8235\n8236\n8237\n8238\n8239\n8240\n8241\n8242\n8243\n8244\n8245\n8246\n8247\n8248\n8249\n8250\n8251\n8252\n8253\n8254\n8255\n8256\n8257\n8258\n8259\n8260\n8261\n8262\n8263\n8264\n8265\n8266\n8267\n8268\n8269\n8270\n8271\n8272\n8273\n8274\n8275\n8276\n8277\n8278\n8279\n8280\n8281\n8282\n8283\n8284\n8285\n8286\n8287\n8288\n8289\n8290\n8291\n8292\n8293\n8294\n8295\n8296\n8297\n8298\n8299\n8300\n8301\n8302\n8303\n8304\n8305\n8306\n8307\n8308\n8309\n8310\n8311\n8312\n8313\n8314\n8315\n8316\n8317\n8318\n8319\n8320\n8321\n8322\n8323\n8324\n8325\n8326\n8327\n8328\n8329\n8330\n8331\n8332\n8333\n8334\n8335\n8336\n8337\n8338\n8339\n8340\n8341\n8342\n8343\n8344\n8345\n8346\n8347\n8348\n8349\n8350\n8351\n8352\n8353\n8354\n8355\n8356\n8357\n8358\n8359\n8360\n8361\n8362\n8363\n8364\n8365\n8366\n8367\n8368\n8369\n8370\n8371\n8372\n8373\n8374\n8375\n8376\n8377\n8378\n8379\n8380\n8381\n8382\n8383\n8384\n8385\n8386\n8387\n8388\n8389\n8390\n8391\n8392\n8393\n8394\n8395\n8396\n8397\n8398\n8399\n8400\n8401\n8402\n8403\n8404\n8405\n8406\n8407\n8408\n8409\n8410\n8411\n8412\n8413\n8414\n8415\n8416\n8417\n8418\n8419\n8420\n8421\n8422\n8423\n8424\n8425\n8426\n8427\n8428\n8429\n8430\n8431\n8432\n8433\n8434\n8435\n8436\n8437\n8438\n8439\n8440\n8441\n8442\n8443\n8444\n8445\n8446\n8447\n8448\n8449\n8450\n8451\n8452\n8453\n8454\n8455\n8456\n8457\n8458\n8459\n8460\n8461\n8462\n8463\n8464\n8465\n8466\n8467\n8468\n8469\n8470\n8471\n8472\n8473\n8474\n8475\n8476\n8477\n8478\n8479\n8480\n8481\n8482\n8483\n8484\n8485\n8486\n8487\n8488\n8489\n8490\n8491\n8492\n8493\n8494\n8495\n8496\n8497\n8498\n8499\n8500\n8501\n8502\n8503\n8504\n8505\n8506\n8507\n8508\n8509\n8510\n8511\n8512\n8513\n8514\n8515\n8516\n8517\n8518\n8519\n8520\n8521\n8522\n8523\n8524\n8525\n8526\n8527\n8528\n8529\n8530\n8531\n8532\n8533\n8534\n8535\n8536\n8537\n8538\n8539\n8540\n8541\n8542\n8543\n8544\n8545\n8546\n8547\n8548\n8549\n8550\n8551\n8552\n8553\n8554\n8555\n8556\n8557\n8558\n8559\n8560\n8561\n8562\n8563\n8564\n8565\n8566\n8567\n8568\n8569\n8570\n8571\n8572\n8573\n8574\n8575\n8576\n8577\n8578\n8579\n8580\n8581\n8582\n8583\n8584\n8585\n8586\n8587\n8588\n8589\n8590\n8591\n8592\n8593\n8594\n8595\n8596\n8597\n8598\n8599\n8600\n8601\n8602\n8603\n8604\n8605\n8606\n8607\n8608\n8609\n8610\n8611\n8612\n8613\n8614\n8615\n8616\n8617\n8618\n8619\n8620\n8621\n8622\n8623\n8624\n8625\n8626\n8627\n8628\n8629\n8630\n8631\n8632\n8633\n8634\n8635\n8636\n8637\n8638\n8639\n8640\n8641\n8642\n8643\n8644\n8645\n8646\n8647\n8648\n8649\n8650\n8651\n8652\n8653\n8654\n8655\n8656\n8657\n8658\n8659\n8660\n8661\n8662\n8663\n8664\n8665\n8666\n8667\n8668\n8669\n8670\n8671\n8672\n8673\n8674\n8675\n8676\n8677\n8678\n8679\n8680\n8681\n8682\n8683\n8684\n8685\n8686\n8687\n8688\n8689\n8690\n8691\n8692\n8693\n8694\n8695\n8696\n8697\n8698\n8699\n8700\n8701\n8702\n8703\n8704\n8705\n8706\n8707\n8708\n8709\n8710\n8711\n8712\n8713\n8714\n8715\n8716\n8717\n8718\n8719\n8720\n8721\n8722\n8723\n8724\n8725\n8726\n8727\n8728\n8729\n8730\n8731\n8732\n8733\n8734\n8735\n8736\n8737\n8738\n8739\n8740\n8741\n8742\n8743\n8744\n8745\n8746\n8747\n8748\n8749\n8750\n8751\n8752\n8753\n8754\n8755\n8756\n8757\n8758\n8759\n8760\n8761\n8762\n8763\n8764\n8765\n8766\n8767\n8768\n8769\n8770\n8771\n8772\n8773\n8774\n8775\n8776\n8777\n8778\n8779\n8780\n8781\n8782\n8783\n8784\n8785\n8786\n8787\n8788\n8789\n8790\n8791\n8792\n8793\n8794\n8795\n8796\n8797\n8798\n8799\n8800\n8801\n8802\n8803\n8804\n8805\n8806\n8807\n8808\n8809\n8810\n8811\n8812\n8813\n8814\n8815\n8816\n8817\n8818\n8819\n8820\n8821\n8822\n8823\n8824\n8825\n8826\n8827\n8828\n8829\n8830\n8831\n8832\n8833\n8834\n8835\n8836\n8837\n8838\n8839\n8840\n8841\n8842\n8843\n8844\n8845\n8846\n8847\n8848\n8849\n8850\n8851\n8852\n8853\n8854\n8855\n8856\n8857\n8858\n8859\n8860\n8861\n8862\n8863\n8864\n8865\n8866\n8867\n8868\n8869\n8870\n8871\n8872\n8873\n8874\n8875\n8876\n8877\n8878\n8879\n8880\n8881\n8882\n8883\n8884\n8885\n8886\n8887\n8888\n8889\n8890\n8891\n8892\n8893\n8894\n8895\n8896\n8897\n8898\n8899\n8900\n8901\n8902\n8903\n8904\n8905\n8906\n8907\n8908\n8909\n8910\n8911\n8912\n8913\n8914\n8915\n8916\n8917\n8918\n8919\n8920\n8921\n8922\n8923\n8924\n8925\n8926\n8927\n8928\n8929\n8930\n8931\n8932\n8933\n8934\n8935\n8936\n8937\n8938\n8939\n8940\n8941\n8942\n8943\n8944\n8945\n8946\n8947\n8948\n8949\n8950\n8951\n8952\n8953\n8954\n8955\n8956\n8957\n8958\n8959\n8960\n8961\n8962\n8963\n8964\n8965\n8966\n8967\n8968\n8969\n8970\n8971\n8972\n8973\n8974\n8975\n8976\n8977\n8978\n8979\n8980\n8981\n8982\n8983\n8984\n8985\n8986\n8987\n8988\n8989\n8990\n8991\n8992\n8993\n8994\n8995\n8996\n8997\n8998\n8999\n9000\n9001\n9002\n9003\n9004\n9005\n9006\n9007\n9008\n9009\n9010\n9011\n9012\n9013\n9014\n9015\n9016\n9017\n9018\n9019\n9020\n9021\n9022\n9023\n9024\n9025\n9026\n9027\n9028\n9029\n9030\n9031\n9032\n9033\n9034\n9035\n9036\n9037\n9038\n9039\n9040\n9041\n9042\n9043\n9044\n9045\n9046\n9047\n9048\n9049\n9050\n9051\n9052\n9053\n9054\n9055\n9056\n9057\n9058\n9059\n9060\n9061\n9062\n9063\n9064\n9065\n9066\n9067\n9068\n9069\n9070\n9071\n9072\n9073\n9074\n9075\n9076\n9077\n9078\n9079\n9080\n9081\n9082\n9083\n9084\n9085\n9086\n9087\n9088\n9089\n9090\n9091\n9092\n9093\n9094\n9095\n9096\n9097\n9098\n9099\n9100\n9101\n9102\n9103\n9104\n9105\n9106\n9107\n9108\n9109\n9110\n9111\n9112\n9113\n9114\n9115\n9116\n9117\n9118\n9119\n9120\n9121\n9122\n9123\n9124\n9125\n9126\n9127\n9128\n9129\n9130\n9131\n9132\n9133\n9134\n9135\n9136\n9137\n9138\n9139\n9140\n9141\n9142\n9143\n9144\n9145\n9146\n9147\n9148\n9149\n9150\n9151\n9152\n9153\n9154\n9155\n9156\n9157\n9158\n9159\n9160\n9161\n9162\n9163\n9164\n9165\n9166\n9167\n9168\n9169\n9170\n9171\n9172\n9173\n9174\n9175\n9176\n9177\n9178\n9179\n9180\n9181\n9182\n9183\n9184\n9185\n9186\n9187\n9188\n9189\n9190\n9191\n9192\n9193\n9194\n9195\n9196\n9197\n9198\n9199\n9200\n9201\n9202\n9203\n9204\n9205\n9206\n9207\n9208\n9209\n9210\n9211\n9212\n9213\n9214\n9215\n9216\n9217\n9218\n9219\n9220\n9221\n9222\n9223\n9224\n9225\n9226\n9227\n9228\n9229\n9230\n9231\n9232\n9233\n9234\n9235\n9236\n9237\n9238\n9239\n9240\n9241\n9242\n9243\n9244\n9245\n9246\n9247\n9248\n9249\n9250\n9251\n9252\n9253\n9254\n9255\n9256\n9257\n9258\n9259\n9260\n9261\n9262\n9263\n9264\n9265\n9266\n9267\n9268\n9269\n9270\n9271\n9272\n9273\n9274\n9275\n9276\n9277\n9278\n9279\n9280\n9281\n9282\n9283\n9284\n9285\n9286\n9287\n9288\n9289\n9290\n9291\n9292\n9293\n9294\n9295\n9296\n9297\n9298\n9299\n9300\n9301\n9302\n9303\n9304\n9305\n9306\n9307\n9308\n9309\n9310\n9311\n9312\n9313\n9314\n9315\n9316\n9317\n9318\n9319\n9320\n9321\n9322\n9323\n9324\n9325\n9326\n9327\n9328\n9329\n9330\n9331\n9332\n9333\n9334\n9335\n9336\n9337\n9338\n9339\n9340\n9341\n9342\n9343\n9344\n9345\n9346\n9347\n9348\n9349\n9350\n9351\n9352\n9353\n9354\n9355\n9356\n9357\n9358\n9359\n9360\n9361\n9362\n9363\n9364\n9365\n9366\n9367\n9368\n9369\n9370\n9371\n9372\n9373\n9374\n9375\n9376\n9377\n9378\n9379\n9380\n9381\n9382\n9383\n9384\n9385\n9386\n9387\n9388\n9389\n9390\n9391\n9392\n9393\n9394\n9395\n9396\n9397\n9398\n9399\n9400\n9401\n9402\n9403\n9404\n9405\n9406\n9407\n9408\n9409\n9410\n9411\n9412\n9413\n9414\n9415\n9416\n9417\n9418\n9419\n9420\n9421\n9422\n9423\n9424\n9425\n9426\n9427\n9428\n9429\n9430\n9431\n9432\n9433\n9434\n9435\n9436\n9437\n9438\n9439\n9440\n9441\n9442\n9443\n9444\n9445\n9446\n9447\n9448\n9449\n9450\n9451\n9452\n9453\n9454\n9455\n9456\n9457\n9458\n9459\n9460\n9461\n9462\n9463\n9464\n9465\n9466\n9467\n9468\n9469\n9470\n9471\n9472\n9473\n9474\n9475\n9476\n9477\n9478\n9479\n9480\n9481\n9482\n9483\n9484\n9485\n9486\n9487\n9488\n9489\n9490\n9491\n9492\n9493\n9494\n9495\n9496\n9497\n9498\n9499\n9500\n9501\n9502\n9503\n9504\n9505\n9506\n9507\n9508\n9509\n9510\n9511\n9512\n9513\n9514\n9515\n9516\n9517\n9518\n9519\n9520\n9521\n9522\n9523\n9524\n9525\n9526\n9527\n9528\n9529\n9530\n9531\n9532\n9533\n9534\n9535\n9536\n9537\n9538\n9539\n9540\n9541\n9542\n9543\n9544\n9545\n9546\n9547\n9548\n9549\n9550\n9551\n9552\n9553\n9554\n9555\n9556\n9557\n9558\n9559\n9560\n9561\n9562\n9563\n9564\n9565\n9566\n9567\n9568\n9569\n9570\n9571\n9572\n9573\n9574\n9575\n9576\n9577\n9578\n9579\n9580\n9581\n9582\n9583\n9584\n9585\n9586\n9587\n9588\n9589\n9590\n9591\n9592\n9593\n9594\n9595\n9596\n9597\n9598\n9599\n9600\n9601\n9602\n9603\n9604\n9605\n9606\n9607\n9608\n9609\n9610\n9611\n9612\n9613\n9614\n9615\n9616\n9617\n9618\n9619\n9620\n9621\n9622\n9623\n9624\n9625\n9626\n9627\n9628\n9629\n9630\n9631\n9632\n9633\n9634\n9635\n9636\n9637\n9638\n9639\n9640\n9641\n9642\n9643\n9644\n9645\n9646\n9647\n9648\n9649\n9650\n9651\n9652\n9653\n9654\n9655\n9656\n9657\n9658\n9659\n9660\n9661\n9662\n9663\n9664\n9665\n9666\n9667\n9668\n9669\n9670\n9671\n9672\n9673\n9674\n9675\n9676\n9677\n9678\n9679\n9680\n9681\n9682\n9683\n9684\n9685\n9686\n9687\n9688\n9689\n9690\n9691\n9692\n9693\n9694\n9695\n9696\n9697\n9698\n9699\n9700\n9701\n9702\n9703\n9704\n9705\n9706\n9707\n9708\n9709\n9710\n9711\n9712\n9713\n9714\n9715\n9716\n9717\n9718\n9719\n9720\n9721\n9722\n9723\n9724\n9725\n9726\n9727\n9728\n9729\n9730\n9731\n9732\n9733\n9734\n9735\n9736\n9737\n9738\n9739\n9740\n9741\n9742\n9743\n9744\n9745\n9746\n9747\n9748\n9749\n9750\n9751\n9752\n9753\n9754\n9755\n9756\n9757\n9758\n9759\n9760\n9761\n9762\n9763\n9764\n9765\n9766\n9767\n9768\n9769\n9770\n9771\n9772\n9773\n9774\n9775\n9776\n9777\n9778\n9779\n9780\n9781\n9782\n9783\n9784\n9785\n9786\n9787\n9788\n9789\n9790\n9791\n9792\n9793\n9794\n9795\n9796\n9797\n9798\n9799\n9800\n9801\n9802\n9803\n9804\n9805\n9806\n9807\n9808\n9809\n9810\n9811\n9812\n9813\n9814\n9815\n9816\n9817\n9818\n9819\n9820\n9821\n9822\n9823\n9824\n9825\n9826\n9827\n9828\n9829\n9830\n9831\n9832\n9833\n9834\n9835\n9836\n9837\n9838\n9839\n9840\n9841\n9842\n9843\n9844\n9845\n9846\n9847\n9848\n9849\n9850\n9851\n9852\n9853\n9854\n9855\n9856\n9857\n9858\n9859\n9860\n9861\n9862\n9863\n9864\n9865\n9866\n9867\n9868\n9869\n9870\n9871\n9872\n9873\n9874\n9875\n9876\n9877\n9878\n9879\n9880\n9881\n9882\n9883\n9884\n9885\n9886\n9887\n9888\n9889\n9890\n9891\n9892\n9893\n9894\n9895\n9896\n9897\n9898\n9899\n9900\n9901\n9902\n9903\n9904\n9905\n9906\n9907\n9908\n9909\n9910\n9911\n9912\n9913\n9914\n9915\n9916\n9917\n9918\n9919\n9920\n9921\n9922\n9923\n9924\n9925\n9926\n9927\n9928\n9929\n9930\n9931\n9932\n9933\n9934\n9935\n9936\n9937\n9938\n9939\n9940\n9941\n9942\n9943\n9944\n9945\n9946\n9947\n9948\n9949\n9950\n9951\n9952\n9953\n9954\n9955\n9956\n9957\n9958\n9959\n9960\n9961\n9962\n9963\n9964\n9965\n9966\n9967\n9968\n9969\n9970\n9971\n9972\n9973\n9974\n9975\n9976\n9977\n9978\n9979\n9980\n9981\n9982\n9983\n9984\n9985\n9986\n9987\n9988\n9989\n9990\n9991\n9992\n9993\n9994\n9995\n9996\n9997\n9998\n9999\n10000\n"
  },
  {
    "path": "test/highlight/number-lines/full-relative/rc",
    "content": "add-highlighter window/ number-lines -full-relative\n"
  },
  {
    "path": "test/highlight/number-lines/full-relative/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4988\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4989\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4990\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 9│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4991\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 8│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4992\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 7│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4993\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 6│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4994\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 5│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4995\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 4│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4996\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 3│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4997\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 2│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4998\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4999\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 0│\" }, { \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"500\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5001\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 2│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5002\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 3│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5003\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 4│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5004\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 5│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5005\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 6│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5006\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 7│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5007\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 8│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5008\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 9│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5009\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5010\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5011\\u000a\" }]], { \"line\": 12, \"column\": 6 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\n"
  },
  {
    "path": "test/highlight/ranges/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/ranges/in",
    "content": "foo\nbar\nbaz\n%( )\n"
  },
  {
    "path": "test/highlight/ranges/rc",
    "content": "declare-option range-specs r\nadd-highlighter window/ ranges r\nset window r %val{timestamp} 1.1+0|default,red 2.1,2.1|default,red 3.1+2|default,red\n"
  },
  {
    "path": "test/highlight/ranges/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ar\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ba\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"z\\u000a\" }], [{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 3, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/highlight/regions/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/regions/in",
    "content": "\"abcdefgh\" hehe ${ youhou{hihi} } <a></b></a> hehe\n"
  },
  {
    "path": "test/highlight/regions/rc",
    "content": "add-highlighter window/regions_test regions\nadd-highlighter window/regions_test/code default-region fill yellow\nadd-highlighter window/regions_test/string region %{\"} %{(?<!\\\\)(\\\\\\\\)*\"} fill green\nadd-highlighter window/regions_test/shell region -recurse '\\{' '\\$\\{' '\\}' fill red\nadd-highlighter window/regions_test/tag region -match-capture '<(\\w+)>' '</(\\w+)>' fill blue\n"
  },
  {
    "path": "test/highlight/regions/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"\" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"abcdefgh\\\"\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" hehe \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"${ youhou{hihi} }\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"<a></b></a>\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" hehe\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/regions-recurse/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/regions-recurse/in",
    "content": "foo(bar '()')\n"
  },
  {
    "path": "test/highlight/regions-recurse/rc",
    "content": "add-highlighter window/regions_test regions\nadd-highlighter window/regions_test/code default-region fill yellow\nadd-highlighter window/regions_test/argument region -recurse '\\(' '\\w+\\h*\\(\\K' '(?=\\))' fill red\n"
  },
  {
    "path": "test/highlight/regions-recurse/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"f\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"oo(\" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar '\"'()'\"'\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-empty-range/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-empty-range/in",
    "content": "12345\n12345\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-empty-range/rc",
    "content": "declare-option range-specs test_ranges %val{timestamp} '1.1+0|A' '1.7+0|B' '2.3+0|C'\nadd-highlighter window/ replace-ranges test_ranges\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-empty-range/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"A\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2345\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"B\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"C\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"345\\u000a\" }]], { \"line\": 0, \"column\": 1 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-large-with-multiple-lines/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-large-with-multiple-lines/in",
    "content": "\n12345\n67890\n%(123\n456\n7890)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-large-with-multiple-lines/rc",
    "content": "declare-option range-specs test_ranges %val{timestamp} '2.1,3.5|.\n.' '4.2,6.3|.\n.'\nadd-highlighter window/ replace-ranges test_ranges\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-large-with-multiple-lines/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \".\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \".\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \".\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \".\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 4, \"column\": 1 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [{ \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"committed change #1\" }], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 6:4 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[+]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range/in",
    "content": "\n12345\n67890\n%(12345\n67890)\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range/rc",
    "content": "declare-option range-specs test_ranges %val{timestamp} '2.1,3.5|..' '4.2,5.4|..'\nadd-highlighter window/ replace-ranges test_ranges\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"..\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"..\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 2, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [{ \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"committed change #1\" }], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 5:5 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[+]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range-pulls-new-lines/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range-pulls-new-lines/in",
    "content": "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range-pulls-new-lines/rc",
    "content": "declare-option range-specs ranges\nadd-highlighter window/ number-lines\nadd-highlighter window/ replace-ranges ranges\nset-option buffer ranges %val{timestamp} 2.1,6.3|..\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range-pulls-new-lines/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 2│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"..\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"07\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 8│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"08\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 9│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"09\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"28│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"28\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"29│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"29\\u000a\" }]], { \"line\": 0, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range-scrolls-fine/cmd",
    "content": "27j<c-l>gj<c-l>gk<c-l>\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range-scrolls-fine/in",
    "content": "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range-scrolls-fine/rc",
    "content": "declare-option range-specs ranges\nadd-highlighter window/ number-lines\nadd-highlighter window/ replace-ranges ranges\nset-option buffer ranges %val{timestamp} 2.1,6.2|.. 35.1,39.2|..\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-multiline-range-scrolls-fine/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"01\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 2│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"..\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 7│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"07\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 8│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"08\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 9│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"09\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"28│\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"8\\u000a\" }]], { \"line\": 23, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"28│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"28\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"29│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"29\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"30│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"30\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"31│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"31\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"32│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"32\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"33│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"33\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"34│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"34\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"35│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"..\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"40│\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\\u000a\" }]], { \"line\": 23, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 2│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"..\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 7│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"07\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 8│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"08\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 9│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"09\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"28│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"28\\u000a\" }]], { \"line\": 0, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-only-fully-selected-ranges/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-only-fully-selected-ranges/in",
    "content": "%(12345)\n1%(234)5\n1%(2)345\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-only-fully-selected-ranges/rc",
    "content": "declare-option range-specs test_ranges %val{timestamp} '1.2,1.4|{blue}replaced{green} text' '2.2,2.4|{blue}replaced{green} text' '3.2,3.4|{red}not {blue}replaced{green} text'\nadd-highlighter window/ replace-ranges test_ranges\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-only-fully-selected-ranges/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"replaced\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" text\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"replaced\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" text\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"345\\u000a\" }]], { \"line\": 2, \"column\": 1 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [{ \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"committed change #1\" }], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 3:2 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[+]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"3 sels (3)\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-with-multiple-lines/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-with-multiple-lines/in",
    "content": "\n12345\n67890\n%(12345\n67890)\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-with-multiple-lines/rc",
    "content": "declare-option range-specs test_ranges %val{timestamp} '2.1,3.5|.\n.' '4.2,5.4|.\n.'\nadd-highlighter window/ replace-ranges test_ranges\n"
  },
  {
    "path": "test/highlight/replace-ranges/replace-with-multiple-lines/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \".\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \".\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \".\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \".\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 4, \"column\": 1 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [{ \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"committed change #1\" }], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 5:5 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[+]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/comment0/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/comment0/in",
    "content": "// make sure the color does not break\n/// ```\n/// # use foo;\n/// foo::bar(\"Hello world\");\n/// ```\nfn bar(s: &str) {\n    println!(\"{}\", s);\n}\n"
  },
  {
    "path": "test/highlight/rust/comment0/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/comment0/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \"/\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/ make sure the color does not break\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"use\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo;\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"Hello world\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"fn\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(s: \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"&str\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \") {\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"println!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"{}\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \", s);\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"}\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/doc_comment_hidden/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/doc_comment_hidden/in",
    "content": "    //! ```\n    //! // not hidden #\n    //! #![feature(x)]\n    //! # // hidden #\n    //! # #![feature(x)]\n    //! ```\n\n    /**\n     * ```\n     * // not hidden #\n     * #![feature(x)]\n     * # // hidden #\n     * # #![feature(x)]\n     * ```\n     */\n"
  },
  {
    "path": "test/highlight/rust/doc_comment_hidden/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/doc_comment_hidden/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"   \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// not hidden #\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#![feature(x)]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// hidden #\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#![feature(x)]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/**\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// not hidden #\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" #![feature(x)]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" #\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// hidden #\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" #\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#![feature(x)]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     */\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/doc_comment_hidden_with_empty_line/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/doc_comment_hidden_with_empty_line/in",
    "content": "    /// ```\n    /// println!(\"empty line\");\n    ///\n    /// println!(\"continue line\");\n    /// ```\n\n    /**\n     * ```\n     * println!(\"empty line\");\n     *\n     * println!(\"continue line\");\n     * ```\n     */\n"
  },
  {
    "path": "test/highlight/rust/doc_comment_hidden_with_empty_line/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/doc_comment_hidden_with_empty_line/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"   \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"println!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"empty line\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"println!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"continue line\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/**\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"println!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"empty line\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"println!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"continue line\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     */\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/inner_block_comment1/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/inner_block_comment1/in",
    "content": "    /***\n     * ```\n     * foo::bar(\"Hello world\");\n     * ```\n     */\n\n    /*\n     * ```\n     * foo::bar(\"Hello world\");\n     * ```\n     */\n"
  },
  {
    "path": "test/highlight/rust/inner_block_comment1/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/inner_block_comment1/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"   \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/***\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * ```\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * foo::bar(\\\"Hello world\\\");\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * ```\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     */\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/*\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * ```\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * foo::bar(\\\"Hello world\\\");\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * ```\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     */\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/inner_block_doc_comment1/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/inner_block_doc_comment1/in",
    "content": "    /**\n     * ```shell\n     * $ cargo install\n     * ```\n     *\n     * ```edition2018,no_run\n     * # use foo;\n     * foo::bar(\"Hello world\");\n     * ```\n     */\n\n    /**\n     ```\n     # use foo;\n     foo::bar(\"Hello world\");\n     ```\n     */\n"
  },
  {
    "path": "test/highlight/rust/inner_block_doc_comment1/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/inner_block_doc_comment1/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"   \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/**\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * ```shell\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * $ cargo install\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * ```\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     *\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     * \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```edition2018\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \",\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"no_run\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" #\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"use\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo;\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"Hello world\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     */\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/**\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     #\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"use\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo;\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"Hello world\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"     */\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/inner_line_doc_comment1/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/inner_line_doc_comment1/in",
    "content": "    /// ```shell\n    /// $ cargo install\n    /// ```\n    ///\n    /// ```edition2018,no_run\n    /// # use foo;\n    /// foo::bar(\"Hello world\");\n    /// ```\n    ///\n    /// ```\n    /// # use foo;\n    /// foo::bar(\"Hello world\");\n    /// ```\n"
  },
  {
    "path": "test/highlight/rust/inner_line_doc_comment1/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/inner_line_doc_comment1/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"   /// ```shell\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    /// $ cargo install\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    /// ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```edition2018\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \",\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"no_run\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"use\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo;\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"Hello world\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"use\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo;\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"Hello world\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"///\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/let/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/let/in",
    "content": "let foo = \"bar\";\nlet mut foo = \"bar\";\nlet (foo, bar) = (\"baz\", \"quux\");\nlet (mut foo, mut bar) = (\"baz\", \"quux\");\n"
  },
  {
    "path": "test/highlight/rust/let/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/let/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \"l\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"et\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"=\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"bar\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \";\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"let\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"mut\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"=\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"bar\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \";\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"let\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(foo, bar) \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"=\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" (\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"baz\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \", \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"quux\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"let\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"mut\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo, \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"mut\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" bar) \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"=\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" (\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"baz\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \", \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"quux\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/line_comment1/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/line_comment1/in",
    "content": "    //!! ```\n    //!! foo::bar(\"Hello world\");\n    //!! ```\n\n    //// ```\n    //// foo::bar(\"Hello world\");\n    //// ```\n\n    // ```\n    // foo::bar(\"Hello world\");\n    // ```\n"
  },
  {
    "path": "test/highlight/rust/line_comment1/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/line_comment1/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"   \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!! ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!! foo::bar(\\\"Hello world\\\");\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!! ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//// ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//// foo::bar(\\\"Hello world\\\");\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//// ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// foo::bar(\\\"Hello world\\\");\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/outer_block_doc_comment0/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/outer_block_doc_comment0/in",
    "content": "//! # Hello World\n//!\n//! ```shell\n//! $ cargo install\n//! ```\n//!\n//! ```\n//! # use foo;\n//! foo::bar(\"Hello world\");\n//! ```\n\n//!! ```\n//!! foo::bar(\"Hello world\");\n//!! ```\n"
  },
  {
    "path": "test/highlight/rust/outer_block_doc_comment0/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/outer_block_doc_comment0/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \"/\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/! # Hello World\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//! ```shell\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//! $ cargo install\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//! ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"use\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo;\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"Hello world\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \");\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!! ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!! foo::bar(\\\"Hello world\\\");\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"//!! ```\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/pub/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/pub/in",
    "content": "// bare\npub\n\n// with scope\npub(crate)\npub (crate)\npub(super)\npub(self)\npub(in ::foo)\npub(in foo)\npub(in foo::bar)\npub(in foo::bar::baz)\n\n// not valid pub expression, parens not colored\npub(crat)\npub(in)\nspub(crate)\n"
  },
  {
    "path": "test/highlight/rust/pub/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/pub/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \"/\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/ bare\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// with scope\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"crate\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"crate\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"super\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"self\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"in\" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"in\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"in\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"in\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"::\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"baz\" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// not valid pub expression, parens not colored\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(crat)\\u000a\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"pub\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"in\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"spub\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"(\" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"crate\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \")\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/todo/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/todo/in",
    "content": "// TODO: some totally awesome comment\n// NOTE: some note\n// FIXME: fix some bug\n\n/*\n * TODO: some totally awesome comment\n * NOTE: some note\n * FIXME: fix some bug\n */\n"
  },
  {
    "path": "test/highlight/rust/todo/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/todo/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \"/\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/ \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"TODO\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \": some totally awesome comment\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"NOTE\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \": some note\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"// \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"FIXME\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \": fix some bug\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"/*\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" * \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"TODO\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \": some totally awesome comment\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" * \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"NOTE\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \": some note\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" * \" }, { \"face\": { \"fg\": \"magenta\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"FIXME\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \": fix some bug\\u000a\" }], [{ \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" */\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/rust/value/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/rust/value/in",
    "content": "b'\\' b'\\x00' b'0' b'a' b' ' b'!' b'\\n' b'一' b'\\u{12}'\n'\\x00' '0' 'a' ' ' '!' '\\n' '\\u{12}' '一'\n"
  },
  {
    "path": "test/highlight/rust/value/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/highlight/rust/value/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \"b\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''\\\\'\\'' \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b'\\''\\\\x00'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b'\\''0'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b'\\''a'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b'\\'' '\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b'\\''!'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b'\\''\\\\n'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" b\" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''一'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" b\" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''\\\\u{12}'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''\\\\x00'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''0'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''a'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\'' '\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''!'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''\\\\n'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''\\\\u{12}'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"'\\''一'\\''\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/in",
    "content": "xxxxxxxx1\t2\t3\t4\t5\t6\t7\t8\t9\t10%(\t)\n"
  },
  {
    "path": "test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"3\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"6\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"7\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"8\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"9\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"      \" }]], { \"line\": 0, \"column\": 74 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/highlight/wrap/avoid-odd-places/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/wrap/avoid-odd-places/in",
    "content": "This is a long paragraph where we will see if the wrap highlighter tries to \"Wrap\" between the quotes and the word.\n"
  },
  {
    "path": "test/highlight/wrap/avoid-odd-places/rc",
    "content": "add-highlighter window/ wrap -word\n"
  },
  {
    "path": "test/highlight/wrap/avoid-odd-places/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"T\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"his is a long paragraph where we will see if the wrap highlighter tries to \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"Wrap\\\" between the quotes and the word.\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/wrap/basic/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/wrap/basic/in",
    "content": "--------------------------------------------------------------------------------wrap\n--------------------------------------------------------------------------------wrap----------------------------------------------------------------------------wrap\n"
  },
  {
    "path": "test/highlight/wrap/basic/rc",
    "content": "add-highlighter window/ wrap\n"
  },
  {
    "path": "test/highlight/wrap/basic/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-------------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"--------------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap----------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-number-lines/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-number-lines/in",
    "content": "line 1\nline 2\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-number-lines/rc",
    "content": "add-highlighter window/ wrap -word -width 4\nadd-highlighter window/ number-lines\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-number-lines/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"l\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ine\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"italic\"] }, \"contents\": \" 1\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 2│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"line\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"italic\"] }, \"contents\": \" 2\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 2\\u000a\" }]], { \"line\": 0, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-replace-ranges/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-replace-ranges/in",
    "content": "------------------------------------------------------------------------- wrap\n-------------------------------------------------------------------------\nprefix replaced wrapped\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-replace-ranges/rc",
    "content": "declare-option range-specs ranges %val{timestamp} '1.1+0|HINT:' '2.74+0|WRAPPED HINT' '3.8+8|OVERFLOWING HINT XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'\n\nadd-highlighter window/ wrap -word\nadd-highlighter window/ replace-ranges ranges\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-replace-ranges/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"HINT:\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"------------------------------------------------------------------------ \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"WRAPPED HINT\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"prefix \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"OVERFLOWING HINT XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" wrapped\\u000a\" }]], { \"line\": 0, \"column\": 5 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-show-whitespaces/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-show-whitespaces/in",
    "content": "foo bar\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-show-whitespaces/rc",
    "content": "add-highlighter window/ wrap -word -width 5\nadd-highlighter window/ show-whitespaces\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-show-whitespaces/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"f\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"oo\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"·\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-tabulation/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-tabulation/in",
    "content": "----------------------------------------------------------------------------\t\t\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-tabulation/rc",
    "content": "add-highlighter window/ number-lines\nadd-highlighter window/ wrap\n"
  },
  {
    "path": "test/highlight/wrap/interact-with-tabulation/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"---------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"italic\"] }, \"contents\": \" 1\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"            \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n\n"
  },
  {
    "path": "test/highlight/wrap/marker-and-indent/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/wrap/marker-and-indent/in",
    "content": "--------------------------------------------------------------------------------wrap\n--------------------------------------------------------------------------------wrap-------------------------------------------------------------------------wrap\n    ----------------------------------------------------------------------------wrap\n    ----------------------------------------------------------------------------wrap------------------------------------------------------------------------wrap\n"
  },
  {
    "path": "test/highlight/wrap/marker-and-indent/rc",
    "content": "add-highlighter window/ wrap -marker '>>>' -indent\n"
  },
  {
    "path": "test/highlight/wrap/marker-and-indent/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-------------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \">>>\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"--------------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \">>>\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap-------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \">>>\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    ----------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \">>>\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    ----------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \">>>\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \">>>\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n\n"
  },
  {
    "path": "test/highlight/wrap/marker-interact-with-long-replace-ranges/cmd",
    "content": "ge\n"
  },
  {
    "path": "test/highlight/wrap/marker-interact-with-long-replace-ranges/in",
    "content": "\n"
  },
  {
    "path": "test/highlight/wrap/marker-interact-with-long-replace-ranges/rc",
    "content": "declare-option range-specs ranges\nset-option window ranges %val{timestamp} \"1.1+0|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"\nadd-highlighter window/ wrap -marker \"x\"\nadd-highlighter window/ replace-ranges ranges\n"
  },
  {
    "path": "test/highlight/wrap/marker-interact-with-long-replace-ranges/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"x\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 1, \"column\": 1 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/highlight/wrap/word/cmd",
    "content": "\n"
  },
  {
    "path": "test/highlight/wrap/word/in",
    "content": "123456789 wrap\n123456 wrap\n123456 wrap 1234 wrap 123456789012 wrap\n"
  },
  {
    "path": "test/highlight/wrap/word/rc",
    "content": "add-highlighter window/ wrap -word -width 10\n"
  },
  {
    "path": "test/highlight/wrap/word/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23456789 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"123456 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"123456 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap 1234 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap 12345\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"6789012 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wrap\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n\n"
  },
  {
    "path": "test/hooks/completion-hide/cmd",
    "content": ":update-completions<ret>\ni<c-n>a<ret><esc>\n:update-completions<ret>\ni<c-n><c-p>b<esc>\n"
  },
  {
    "path": "test/hooks/completion-hide/out",
    "content": "accepted completion: <foo()>a\nrejected completion b\n"
  },
  {
    "path": "test/hooks/completion-hide/rc",
    "content": "declare-option completions line1\ndeclare-option completions line2\nset-option global completers \\\n\toption=line1 \\\n\toption=line2 \\\n\ndefine-command update-completions %{\n    set-option global line1 \"1.1@%val{timestamp}\" foo()||\n    set-option global line2 \"2.1@%val{timestamp}\" foo()||\n}\n\nhook global InsertCompletionHide .+ %{\n    evaluate-commands -draft %{\n        select %val{hook_param}\n        execute-keys i<lt><esc>a<gt><esc>\n\texecute-keys <a-h>i \"accepted completion: \"\n    }\n}\nhook global InsertCompletionHide '' %{\n    evaluate-commands -draft %{\n        execute-keys <a-h>i \"rejected completion \"\n    }\n}\n"
  },
  {
    "path": "test/hooks/completion-hide-using-register/cmd",
    "content": "\n"
  },
  {
    "path": "test/hooks/completion-hide-using-register/in",
    "content": "echo\n"
  },
  {
    "path": "test/hooks/completion-hide-using-register/out",
    "content": "echofoobar\necho\n"
  },
  {
    "path": "test/hooks/completion-hide-using-register/rc",
    "content": "set-option global autocomplete insert\nhook global InsertCompletionHide .+ %{\n    evaluate-commands -draft -save-regs '\"' %{\n        select %val{hook_param}\n        set-register dquote foo bar\n        execute-keys <a-p>\n    }\n}\n"
  },
  {
    "path": "test/hooks/completion-hide-using-register/script",
    "content": "ui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"Oe\" ] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_show\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"echo\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"inline\"] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"<c-n>\" ] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_select\", \"params\": [1] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"<c-r>\\\"\" ] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\n"
  },
  {
    "path": "test/hooks/module-loaded/cmd",
    "content": "\"a<a-P>i<space><esc>\n"
  },
  {
    "path": "test/hooks/module-loaded/out",
    "content": " literal regex regex-once regex literal-late late-regex late-regex late-regex-once\n"
  },
  {
    "path": "test/hooks/module-loaded/rc",
    "content": "provide-module foo %{ }\nprovide-module foobar %{ }\nhook global ModuleLoaded foo %{ set-register a %reg{a} literal }\nhook global ModuleLoaded f.* %{ set-register a %reg{a} regex }\nhook -once global ModuleLoaded f.* %{ set-register a %reg{a} regex-once }\nrequire-module foo\nrequire-module foobar\nhook global ModuleLoaded foo %{ set-register a %reg{a} literal-late }\nhook global ModuleLoaded f.* %{ set-register a %reg{a} late-regex }\nhook -once global ModuleLoaded f.* %{ set-register a %reg{a} late-regex-once }\n"
  },
  {
    "path": "test/hooks/once/cmd",
    "content": "iaaa<esc>\n"
  },
  {
    "path": "test/hooks/once/out",
    "content": "abaa\n"
  },
  {
    "path": "test/hooks/once/rc",
    "content": "hook global -once InsertChar a 'exec b'\n"
  },
  {
    "path": "test/hooks/remove-regex/cmd",
    "content": "if<esc>:remove-hooks global group[12]<ret>if<esc>\n"
  },
  {
    "path": "test/hooks/remove-regex/out",
    "content": "foof\n"
  },
  {
    "path": "test/hooks/remove-regex/rc",
    "content": "hook global -group group1 InsertChar f %{ exec o }\nhook global -group group2 InsertChar f %{ exec o }\n"
  },
  {
    "path": "test/indent/c-family/align-while-expr/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/indent/c-family/align-while-expr/in",
    "content": "while (a < b and%( )b >= c)\n"
  },
  {
    "path": "test/indent/c-family/align-while-expr/out",
    "content": "while (a < b and\n       b >= c)\n"
  },
  {
    "path": "test/indent/c-family/align-while-expr/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/close-block/cmd",
    "content": "a<ret>}<esc>\n"
  },
  {
    "path": "test/indent/c-family/close-block/in",
    "content": "template<typename T> struct A : B\n{\n    struct C\n    {\n        void foo() {\n            bar()%(;)\n    }%(;)\n\nstruct D {\n    void baz() %({)\n    int qux%(;)\n\nif (true) {\n    if (true) {\n        foo%(;)\n    bar%(;)\n\n    foo(bar) {}\n{\n    baz%(;)\n"
  },
  {
    "path": "test/indent/c-family/close-block/out",
    "content": "template<typename T> struct A : B\n{\n    struct C\n    {\n        void foo() {\n            bar();\n        }\n    };\n};\n\nstruct D {\n    void baz() {\n    }\n    int qux;\n};\n\nif (true) {\n    if (true) {\n        foo;\n    }\n    bar;\n}\n\n    foo(bar) {}\n{\n    baz;\n}\n"
  },
  {
    "path": "test/indent/c-family/close-block/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/close-function-with-struct-param/cmd",
    "content": "c<ret>}<esc>\n"
  },
  {
    "path": "test/indent/c-family/close-function-with-struct-param/in",
    "content": "void f(int i,\n       struct S s)\n{\n%( )\n\nvoid g(int i,\n       struct S s) {\n%( )\n\nstruct T {\n    void h(int i,\n           struct S s) const {\n    %( )\n\n    void i(int i,\n           struct S s) const final {\n    %( )\n};\n"
  },
  {
    "path": "test/indent/c-family/close-function-with-struct-param/out",
    "content": "void f(int i,\n       struct S s)\n{\n\n}\n\nvoid g(int i,\n       struct S s) {\n\n}\n\nstruct T {\n    void h(int i,\n           struct S s) const {\n\n    }\n\n    void i(int i,\n           struct S s) const final {\n\n    }\n};\n"
  },
  {
    "path": "test/indent/c-family/close-function-with-struct-param/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/close-namespace/cmd",
    "content": "a<ret>}<esc>\n"
  },
  {
    "path": "test/indent/c-family/close-namespace/in",
    "content": "namespace A\n{\n    struct B%(;)\n"
  },
  {
    "path": "test/indent/c-family/close-namespace/out",
    "content": "namespace A\n{\n    struct B;\n}\n"
  },
  {
    "path": "test/indent/c-family/close-namespace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/comment-line-continuation/cmd",
    "content": "c<ret>bar<ret><ret><esc>\n"
  },
  {
    "path": "test/indent/c-family/comment-line-continuation/in",
    "content": "/* foo%( )\n\n// foo%( )\n"
  },
  {
    "path": "test/indent/c-family/comment-line-continuation/out",
    "content": "/* foo\n * bar\n */\n\n\n// foo\n// bar\n\n"
  },
  {
    "path": "test/indent/c-family/comment-line-continuation/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/construct-align/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/indent/c-family/construct-align/in",
    "content": "MyObject obj{param1,%( )param2};\n"
  },
  {
    "path": "test/indent/c-family/construct-align/out",
    "content": "MyObject obj{param1,\n             param2};\n"
  },
  {
    "path": "test/indent/c-family/construct-align/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/deindent-function-closing-brace/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/c-family/deindent-function-closing-brace/in",
    "content": "void foo(void) {%( )}\n\nvoid foo(void) {%( )\n}\n\nvoid foo(void) {%( )bar()}\n\nvoid foo(void) {%( )bar()\n}\n\nvoid foo(void) {\n    bar()%( )}\n\n    void foo(void) {\n                 bar()%( )}\n"
  },
  {
    "path": "test/indent/c-family/deindent-function-closing-brace/out",
    "content": "void foo(void) {\n}\n\nvoid foo(void) {\n    \n}\n\nvoid foo(void) {\n    bar()}\n\nvoid foo(void) {\n    bar()\n}\n\nvoid foo(void) {\n    bar()\n}\n\n    void foo(void) {\n                 bar()\n    }\n"
  },
  {
    "path": "test/indent/c-family/deindent-function-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/deindent-generic-closing-brace/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/c-family/deindent-generic-closing-brace/in",
    "content": "{%( )}\n\n{%( )\n}\n\n{%( )bar()}\n\n{%( )bar()\n}\n\n{\n    bar()%( )}\n\n{(%( ))}\n\n{(%( )\n)}\n\n{(%( )foo())}\n\n{(%( )foo()\n)}\n\n{(\n    bar()%( ))}\n\n    {\n                   bar()%( )}\n\n    {(\n                   bar()%( ))}\n"
  },
  {
    "path": "test/indent/c-family/deindent-generic-closing-brace/out",
    "content": "{\n}\n\n{\n    \n}\n\n{\n    bar()}\n\n{\n    bar()\n}\n\n{\n    bar()\n}\n\n{(\n)}\n\n{(\n    \n)}\n\n{(\n    foo())}\n\n{(\n    foo()\n)}\n\n{(\n    bar()\n)}\n\n    {\n                   bar()\n    }\n\n    {(\n                   bar()\n    )}\n"
  },
  {
    "path": "test/indent/c-family/deindent-generic-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/deindent-if-body/cmd",
    "content": "c<ret>baz<esc>\n"
  },
  {
    "path": "test/indent/c-family/deindent-if-body/in",
    "content": "if (int i = bar())\n    foo();%( )\n\nif (bar(a, b,\n        c, d))\n    foo(); // comment%( )\n\nif (bar(a, b,\n        c, d)) // comment\n    foo();%( )\n\nelse\n    bar();%( )\n\nelse // comment\n    bar();%( )\n"
  },
  {
    "path": "test/indent/c-family/deindent-if-body/out",
    "content": "if (int i = bar())\n    foo();\nbaz\n\nif (bar(a, b,\n        c, d))\n    foo(); // comment\nbaz\n\nif (bar(a, b,\n        c, d)) // comment\n    foo();\nbaz\n\nelse\n    bar();\nbaz\n\nelse // comment\n    bar();\nbaz\n"
  },
  {
    "path": "test/indent/c-family/deindent-if-body/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/deindent-if-closing-brace/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/c-family/deindent-if-closing-brace/in",
    "content": "if (1) {%( )}\n\nif (1) {%( )\n}\n\nif (1) {%( )bar()}\n\nif (1) {%( )bar()\n}\n\nif (1) {\n    bar()%( )}\n\n  if (1) {\n         bar()%( )}\n\n        foo(bar) {}\n    {\n        baz%( )}\n"
  },
  {
    "path": "test/indent/c-family/deindent-if-closing-brace/out",
    "content": "if (1) {\n}\n\nif (1) {\n    \n}\n\nif (1) {\n    bar()}\n\nif (1) {\n    bar()\n}\n\nif (1) {\n    bar()\n}\n\n  if (1) {\n         bar()\n  }\n\n        foo(bar) {}\n    {\n        baz\n    }\n"
  },
  {
    "path": "test/indent/c-family/deindent-if-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/indent-after-parenthesis/cmd",
    "content": "c<ret>foo<esc>\n"
  },
  {
    "path": "test/indent/c-family/indent-after-parenthesis/in",
    "content": "foobarbaz(%( )qux\n"
  },
  {
    "path": "test/indent/c-family/indent-after-parenthesis/out",
    "content": "foobarbaz(\n    fooqux\n"
  },
  {
    "path": "test/indent/c-family/indent-after-parenthesis/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/indent-else-brace/cmd",
    "content": "c<ret>{<ret>foo<esc>\n"
  },
  {
    "path": "test/indent/c-family/indent-else-brace/in",
    "content": "if (condition)\n    foo();\nelse%( )\n"
  },
  {
    "path": "test/indent/c-family/indent-else-brace/out",
    "content": "if (condition)\n    foo();\nelse\n{\n    foo\n"
  },
  {
    "path": "test/indent/c-family/indent-else-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/indent-if-body/cmd",
    "content": "c<ret>foo<esc>\n"
  },
  {
    "path": "test/indent/c-family/indent-if-body/in",
    "content": "if (int i = bar())%( )\n\nif (bar(a, b,\n        c, d))%( )\n\nif (bar(a, b,\n        c, d)) // comment%( )\n\nelse%( )\n\nelse // comment%( )\n"
  },
  {
    "path": "test/indent/c-family/indent-if-body/out",
    "content": "if (int i = bar())\n    foo\n\nif (bar(a, b,\n        c, d))\n    foo\n\nif (bar(a, b,\n        c, d)) // comment\n    foo\n\nelse\n    foo\n\nelse // comment\n    foo\n"
  },
  {
    "path": "test/indent/c-family/indent-if-body/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/indent-if-brace/cmd",
    "content": "c<ret>{<ret>foo<esc>\n"
  },
  {
    "path": "test/indent/c-family/indent-if-brace/in",
    "content": "if (int i = bar())%( )\n"
  },
  {
    "path": "test/indent/c-family/indent-if-brace/kak_selections_desc",
    "content": "3.8,3.8\n"
  },
  {
    "path": "test/indent/c-family/indent-if-brace/out",
    "content": "if (int i = bar())\n{\n    foo\n"
  },
  {
    "path": "test/indent/c-family/indent-if-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/move-brace/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/indent/c-family/move-brace/in",
    "content": "namespace A\n%({)\nstruct B;\n"
  },
  {
    "path": "test/indent/c-family/move-brace/out",
    "content": "namespace A\n\n{\nstruct B;\n"
  },
  {
    "path": "test/indent/c-family/move-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/multiline-nested-align/cmd",
    "content": "c<ret>qiz<esc>\n"
  },
  {
    "path": "test/indent/c-family/multiline-nested-align/in",
    "content": "foo{bar(baz,\n        qux),%( )\n\nfoo(bar{baz,\n        qux},%( )\n"
  },
  {
    "path": "test/indent/c-family/multiline-nested-align/out",
    "content": "foo{bar(baz,\n        qux),\n    qiz\n\nfoo(bar{baz,\n        qux},\n    qiz\n"
  },
  {
    "path": "test/indent/c-family/multiline-nested-align/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/nested-align/cmd",
    "content": "c<ret>qux<esc>\n"
  },
  {
    "path": "test/indent/c-family/nested-align/in",
    "content": "foo{bar(baz,%( )\n\nfoo(bar{baz,%( )\n"
  },
  {
    "path": "test/indent/c-family/nested-align/out",
    "content": "foo{bar(baz,\n        qux\n\nfoo(bar{baz,\n        qux\n"
  },
  {
    "path": "test/indent/c-family/nested-align/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/open-function/cmd",
    "content": "c<ret>foo<esc>\n"
  },
  {
    "path": "test/indent/c-family/open-function/in",
    "content": "void func() {%( )\n"
  },
  {
    "path": "test/indent/c-family/open-function/out",
    "content": "void func() {\n    foo\n"
  },
  {
    "path": "test/indent/c-family/open-function/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/paren-in-literal/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/c-family/paren-in-literal/in",
    "content": "if(c == '(') {%( )\n\nif(c == '(') {}%( )\n\nif(s == \"(\") {%( )\n\nif(s == \"(\" \"foo\") {%( )\n"
  },
  {
    "path": "test/indent/c-family/paren-in-literal/out",
    "content": "if(c == '(') {\n    bar\n\nif(c == '(') {}\nbar\n\nif(s == \"(\") {\n    bar\n\nif(s == \"(\" \"foo\") {\n    bar\n"
  },
  {
    "path": "test/indent/c-family/paren-in-literal/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/c-family/preserve-tabs/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/c-family/preserve-tabs/in",
    "content": "\tif (foo &&%( )\n"
  },
  {
    "path": "test/indent/c-family/preserve-tabs/out",
    "content": "\tif (foo &&\n\t    bar\n"
  },
  {
    "path": "test/indent/c-family/preserve-tabs/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer indentwidth 0\nset buffer filetype cpp\n"
  },
  {
    "path": "test/indent/clojure/doubled-brackets/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/indent/clojure/doubled-brackets/in",
    "content": "((metafn 42)%( )(y 79))\n([weird 42]%( )(y 79))\n({map 42}%( )(y 79))\n"
  },
  {
    "path": "test/indent/clojure/doubled-brackets/out",
    "content": "((metafn 42)\n (y 79))\n([weird 42]\n (y 79))\n({map 42}\n (y 79))\n"
  },
  {
    "path": "test/indent/clojure/doubled-brackets/rc",
    "content": "source \"%val{runtime}/rc/filetype/lisp.kak\"\nsource \"%val{runtime}/rc/filetype/clojure.kak\"\nset buffer filetype clojure\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-indented/cmd",
    "content": "jGex:comment-line<ret>\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-indented/in",
    "content": "a:\n    b: 1\n    c:\n        d: 2\n\n    e: 3\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-indented/out",
    "content": "a:\n    # b: 1\n    # c:\n    #     d: 2\n\n    # e: 3\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-indented/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/yaml.kak\"\nsource \"%val{runtime}/rc/tools/comment.kak\"\nset buffer filetype yaml\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-unindented/cmd",
    "content": "%:comment-line<ret>\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-unindented/in",
    "content": "a:\n    b: 1\n    c:\n        d: 2\n\n    e: 3\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-unindented/out",
    "content": "# a:\n#     b: 1\n#     c:\n#         d: 2\n\n#     e: 3\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-unindented/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/yaml.kak\"\nsource \"%val{runtime}/rc/tools/comment.kak\"\nset buffer filetype yaml\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-when-partially-commented/cmd",
    "content": "%:comment-line<ret>\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-when-partially-commented/in",
    "content": "a:\n    # b: 1\n    c:\n        d: 2\n\n    e: 3\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-when-partially-commented/out",
    "content": "# a:\n#     # b: 1\n#     c:\n#         d: 2\n\n#     e: 3\n"
  },
  {
    "path": "test/indent/comment/comment-multiple-lines-when-partially-commented/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/yaml.kak\"\nsource \"%val{runtime}/rc/tools/comment.kak\"\nset buffer filetype yaml\n"
  },
  {
    "path": "test/indent/comment/uncomment-multiple-indented-lines/cmd",
    "content": "%:comment-line<ret>\n"
  },
  {
    "path": "test/indent/comment/uncomment-multiple-indented-lines/in",
    "content": "# a:\n#     b: 1\n#     c: 2\n"
  },
  {
    "path": "test/indent/comment/uncomment-multiple-indented-lines/out",
    "content": "a:\n    b: 1\n    c: 2\n"
  },
  {
    "path": "test/indent/comment/uncomment-multiple-indented-lines/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/yaml.kak\"\nsource \"%val{runtime}/rc/tools/comment.kak\"\nset buffer filetype yaml\n"
  },
  {
    "path": "test/indent/elixir/do-keyword/cmd",
    "content": "c<ret>f()<esc>jo<ret>def f1() do<ret>1<esc>jo<ret>def f2(), do: 2<ret>\n"
  },
  {
    "path": "test/indent/elixir/do-keyword/in",
    "content": "test do%( )\n"
  },
  {
    "path": "test/indent/elixir/do-keyword/out",
    "content": "test do\n    f()\nend\n\ndef f1() do\n    1\nend\n\ndef f2(), do: 2\n\n"
  },
  {
    "path": "test/indent/elixir/do-keyword/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/elixir.kak\"\nset buffer filetype elixir\n"
  },
  {
    "path": "test/indent/elixir/following-blocks-should-not-prevent-end/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/indent/elixir/following-blocks-should-not-prevent-end/in",
    "content": "def foo() do%( )\n\ndef bar() do\nend\n"
  },
  {
    "path": "test/indent/elixir/following-blocks-should-not-prevent-end/out",
    "content": "def foo() do\n\nend\n\ndef bar() do\nend\n"
  },
  {
    "path": "test/indent/elixir/following-blocks-should-not-prevent-end/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/elixir.kak\"\nset buffer filetype elixir\n"
  },
  {
    "path": "test/indent/elixir/function-definition-shouldnt-duplicate/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/indent/elixir/function-definition-shouldnt-duplicate/in",
    "content": "defmodule Module do\n    def test do%( )\nend\n"
  },
  {
    "path": "test/indent/elixir/function-definition-shouldnt-duplicate/out",
    "content": "defmodule Module do\n    def test do\n\n    end\nend\n"
  },
  {
    "path": "test/indent/elixir/function-definition-shouldnt-duplicate/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/elixir.kak\"\nset buffer filetype elixir\n"
  },
  {
    "path": "test/indent/elixir/insert-comment-hash/cmd",
    "content": "c<ret><esc><a-o>jo# comment3<ret>comment4<esc><a-o>jo#    indented<ret>indented2<esc><a-o>jodef f() do<ret># comment<ret>comment2<esc>j<a-o>jodef f() do<ret># comment<esc>hhhi<ret>\n"
  },
  {
    "path": "test/indent/elixir/insert-comment-hash/in",
    "content": "# Comment %( )comment2\n"
  },
  {
    "path": "test/indent/elixir/insert-comment-hash/out",
    "content": "# Comment\n# comment2\n\n# comment3\n# comment4\n\n#    indented\n#    indented2\n\ndef f() do\n    # comment\n    # comment2\nend\n\ndef f() do\n    # comm\n    # ent\nend\n"
  },
  {
    "path": "test/indent/elixir/insert-comment-hash/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/elixir.kak\"\nset buffer filetype elixir\n"
  },
  {
    "path": "test/indent/fidl/comments/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/fidl/comments/in",
    "content": "    // foo%( )\n\n    // foo%( )bar\n\n    //%( )\n\n    // %( )\n\n    // foo(%( )\n\n    /// foo%( )\n\n    /// foo%( )bar\n\n    ///%( )\n\n    /// %( )\n\n    /// foo(%( )\n"
  },
  {
    "path": "test/indent/fidl/comments/out",
    "content": "    // foo\n    // \n\n    // foo\n    // bar\n\n    //\n    //\n\n    //\n    // \n\n    // foo(\n    // \n\n    /// foo\n    /// \n\n    /// foo\n    /// bar\n\n    ///\n    ///\n\n    ///\n    /// \n\n    /// foo(\n    /// \n"
  },
  {
    "path": "test/indent/fidl/comments/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/fidl.kak\"\nset buffer filetype fidl\n"
  },
  {
    "path": "test/indent/fidl/on-closing/cmd",
    "content": "c<ret>}\n"
  },
  {
    "path": "test/indent/fidl/on-closing/in",
    "content": "    foo {%( )\n\n    foo {%( );\n"
  },
  {
    "path": "test/indent/fidl/on-closing/out",
    "content": "    foo {\n    }\n\n    foo {\n        };\n"
  },
  {
    "path": "test/indent/fidl/on-closing/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/fidl.kak\"\nset buffer filetype fidl\n"
  },
  {
    "path": "test/indent/fidl/on-newline/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/fidl/on-newline/in",
    "content": "    foo%( )\n\n    @foo(%( )\n\n    foo {%( )\n\n    @foo(%( )) {\n\n    @foo(%( )\"bar\") {\n\n    foo {%( )};\n"
  },
  {
    "path": "test/indent/fidl/on-newline/out",
    "content": "    foo\n    \n\n    @foo(\n        \n\n    foo {\n        \n\n    @foo(\n    ) {\n\n    @foo(\n        \"bar\") {\n\n    foo {\n    };\n"
  },
  {
    "path": "test/indent/fidl/on-newline/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/fidl.kak\"\nset buffer filetype fidl\n"
  },
  {
    "path": "test/indent/go/deindent-function-closing-brace/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/go/deindent-function-closing-brace/in",
    "content": "func foo(x int) int {%( )}\n\nfunc foo(x int) int {%( )\n}\n\nfunc foo(x int) int {%( )bar()}\n\nfunc foo(x int) int {%( )bar()\n}\n\nfunc foo(x int) int {\n    bar()%( )}\n\n  func foo(x int) int {\n          bar()%( )}\n"
  },
  {
    "path": "test/indent/go/deindent-function-closing-brace/out",
    "content": "func foo(x int) int {\n}\n\nfunc foo(x int) int {\n    \n}\n\nfunc foo(x int) int {\n    bar()\n}\n\nfunc foo(x int) int {\n    bar()\n}\n\nfunc foo(x int) int {\n    bar()\n}\n\n  func foo(x int) int {\n          bar()\n  }\n"
  },
  {
    "path": "test/indent/go/deindent-function-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/go.kak\"\nset buffer filetype go\n"
  },
  {
    "path": "test/indent/go/deindent-generic-closing-brace/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/go/deindent-generic-closing-brace/in",
    "content": "1\n{%( )}\n\n2\n{%( )\n}\n\n3\n{%( )bar()}\n\n4\n{%( )bar()\n}\n\n5\n{\n    bar()%( )}\n\n6\n  {\n          bar()%( )}\n\n7\n{(%( ))}\n\n8\n{(%( )\n)}\n\n9\n{(%( )foo())}\n\n10\n{(%( )foo()\n)}\n\n11\n{(\n    bar()%( ))}\n\n12\n      {(\n  bar()%( ))}\n\n13\na := []int{\n    someFunction(%( )\n    ),\n}\n\n14\nfoobar(baz,%( )\n\n15\nfoobar{ // some comment%( )\n"
  },
  {
    "path": "test/indent/go/deindent-generic-closing-brace/out",
    "content": "1\n{\n}\n\n2\n{\n    \n}\n\n3\n{\n    bar()\n}\n\n4\n{\n    bar()\n}\n\n5\n{\n    bar()\n}\n\n6\n  {\n          bar()\n  }\n\n7\n{(\n)}\n\n8\n{(\n    \n)}\n\n9\n{(\n    foo()\n)\n}\n\n10\n{(\n    foo()\n)}\n\n11\n{(\n    bar()\n)}\n\n12\n      {(\n  bar()\n      )}\n\n13\na := []int{\n    someFunction(\n        \n    ),\n}\n\n14\nfoobar(baz,\n    \n\n15\nfoobar{ // some comment\n    \n"
  },
  {
    "path": "test/indent/go/deindent-generic-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/go.kak\"\nset buffer filetype go\n"
  },
  {
    "path": "test/indent/go/deindent-if-closing-brace/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/go/deindent-if-closing-brace/in",
    "content": "if true {%( )}\n\nif true {%( )\n}\n\nif true {%( )bar()}\n\nif true {%( )bar()\n}\n\nif true {\n    bar()%( )}\n"
  },
  {
    "path": "test/indent/go/deindent-if-closing-brace/out",
    "content": "if true {\n}\n\nif true {\n    \n}\n\nif true {\n    bar()\n}\n\nif true {\n    bar()\n}\n\nif true {\n    bar()\n}\n"
  },
  {
    "path": "test/indent/go/deindent-if-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/go.kak\"\nset buffer filetype go\n"
  },
  {
    "path": "test/indent/go/insert-comment/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/go/insert-comment/in",
    "content": "1\n// foo(bar,%( )\n\n2\n\t// foo(bar,%( )\n\n3\n// foo{%( )\n\n4\n\t// foo{%( )\n"
  },
  {
    "path": "test/indent/go/insert-comment/out",
    "content": "1\n// foo(bar,\n// \n\n2\n\t// foo(bar,\n\t// \n\n3\n// foo{\n// \n\n4\n\t// foo{\n\t// \n"
  },
  {
    "path": "test/indent/go/insert-comment/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/go.kak\"\nset buffer filetype go\nset-option global disabled_hooks go-closing-delimiter-insert\n"
  },
  {
    "path": "test/indent/haskell/before-comment/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/indent/haskell/before-comment/in",
    "content": "%(-)- comment\n"
  },
  {
    "path": "test/indent/haskell/before-comment/out",
    "content": "\n-- comment\n"
  },
  {
    "path": "test/indent/haskell/before-comment/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/haskell.kak\"\nset buffer filetype haskell\n"
  },
  {
    "path": "test/indent/haskell/indented-comment/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/indent/haskell/indented-comment/in",
    "content": "    -- comment%( )\n"
  },
  {
    "path": "test/indent/haskell/indented-comment/out",
    "content": "    -- comment\n    --\n"
  },
  {
    "path": "test/indent/haskell/indented-comment/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/haskell.kak\"\nset buffer filetype haskell\n"
  },
  {
    "path": "test/indent/haskell/inside-comment/cmd",
    "content": "a<ret><esc>\n"
  },
  {
    "path": "test/indent/haskell/inside-comment/in",
    "content": "    -- inside%( )comment\n"
  },
  {
    "path": "test/indent/haskell/inside-comment/out",
    "content": "    -- inside\n    -- comment\n"
  },
  {
    "path": "test/indent/haskell/inside-comment/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/haskell.kak\"\nset buffer filetype haskell\n"
  },
  {
    "path": "test/indent/html/indent-closing-tag/cmd",
    "content": "o<lt>/div<gt><esc>\n"
  },
  {
    "path": "test/indent/html/indent-closing-tag/in",
    "content": "<div>\n  <div>\n    <div>\n%( )   </div>\n</div>\n"
  },
  {
    "path": "test/indent/html/indent-closing-tag/out",
    "content": "<div>\n  <div>\n    <div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "test/indent/html/indent-closing-tag/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/html.kak\"\nset buffer filetype html\n"
  },
  {
    "path": "test/indent/html/indent-on-new-line/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/indent/html/indent-on-new-line/in",
    "content": "<div>\n    <div>%( )</div>\n</div>\n"
  },
  {
    "path": "test/indent/html/indent-on-new-line/out",
    "content": "<div>\n    <div>\n    </div>\n</div>\n"
  },
  {
    "path": "test/indent/html/indent-on-new-line/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/html.kak\"\nset buffer filetype html\n"
  },
  {
    "path": "test/indent/javascript/deindent-complex-brace-structure/cmd",
    "content": "c<ret><esc>Oif (true) {}<esc>hi<ret><esc>Oconsole.log();<esc>hhi<ret><esc>O{},<ret>{},<esc>hh<a-C>i<ret><esc>1,Ofoo: { bar: 1 },<esc>jjobaz: { bam: 2 },<esc>\n"
  },
  {
    "path": "test/indent/javascript/deindent-complex-brace-structure/in",
    "content": "for (let i = 1; i < 5; ++i) {%(  )}\n"
  },
  {
    "path": "test/indent/javascript/deindent-complex-brace-structure/out",
    "content": "for (let i = 1; i < 5; ++i) {\n    if (true) {\n        console.log(\n            {\n                foo: { bar: 1 },\n            },\n            {\n                baz: { bam: 2 },\n            },\n        );\n    }\n}\n"
  },
  {
    "path": "test/indent/javascript/deindent-complex-brace-structure/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/javascript.kak\"\nset buffer filetype javascript\n"
  },
  {
    "path": "test/indent/lisp/doubled-brackets/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/indent/lisp/doubled-brackets/in",
    "content": "(let ((x 42)%( )(y 79)))\n(let [(x 42)%( )(y 79)])\n(let ([x 42]%( )[y 79]))\n(let ({x 42}%( ){y 79}))\n"
  },
  {
    "path": "test/indent/lisp/doubled-brackets/out",
    "content": "(let ((x 42)\n      (y 79)))\n(let [(x 42)\n      (y 79)])\n(let ([x 42]\n      [y 79]))\n(let ({x 42}\n      {y 79}))\n"
  },
  {
    "path": "test/indent/lisp/doubled-brackets/rc",
    "content": "source \"%val{runtime}/rc/filetype/lisp.kak\"\nset buffer filetype lisp\n"
  },
  {
    "path": "test/indent/lua/insert-end-only-when-needed/cmd",
    "content": "c<ret>foo()<esc>\n"
  },
  {
    "path": "test/indent/lua/insert-end-only-when-needed/in",
    "content": "function fun1()%( )\nend\n\nfunction fun2()%( )\n\nfunction fun3()\n    if true then%( )\nend\n\nfunction fun4()\n    if true then\n        return 10\n    else%( )\nend\n\nfunction fun5()\n    if true then%( )\n        return 10\nend\n\nlocal str = \"while\"%( )\n"
  },
  {
    "path": "test/indent/lua/insert-end-only-when-needed/out",
    "content": "function fun1()\n    foo()\nend\n\nfunction fun2()\n    foo()\nend\n\nfunction fun3()\n    if true then\n        foo()\n    end\nend\n\nfunction fun4()\n    if true then\n        return 10\n    else\n        foo()\n    end\nend\n\nfunction fun5()\n    if true then\n        foo()\n        return 10\nend\n\nlocal str = \"while\"\nfoo()\n"
  },
  {
    "path": "test/indent/lua/insert-end-only-when-needed/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/lua.kak\"\nset buffer filetype lua\n"
  },
  {
    "path": "test/indent/lua/unindent-after-end/cmd",
    "content": "cend<esc>\n"
  },
  {
    "path": "test/indent/lua/unindent-after-end/in",
    "content": "if A then\n        %( )\n\nif A then\n    while B then\n    end\n    %( )\n"
  },
  {
    "path": "test/indent/lua/unindent-after-end/out",
    "content": "if A then\nend\n\nif A then\n    while B then\n    end\nend\n"
  },
  {
    "path": "test/indent/lua/unindent-after-end/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/lua.kak\"\nset buffer filetype lua\n"
  },
  {
    "path": "test/indent/markdown/inside-complex-blockquote/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/indent/markdown/inside-complex-blockquote/in",
    "content": ">> > > block %(q)uote\n"
  },
  {
    "path": "test/indent/markdown/inside-complex-blockquote/out",
    "content": ">> > > block\n>> > > quote\n"
  },
  {
    "path": "test/indent/markdown/inside-complex-blockquote/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/markdown.kak\"\nset buffer filetype markdown\n"
  },
  {
    "path": "test/indent/markdown/inside-list-item/cmd",
    "content": "a<ret><esc>\n"
  },
  {
    "path": "test/indent/markdown/inside-list-item/in",
    "content": "* inside%( )item\n"
  },
  {
    "path": "test/indent/markdown/inside-list-item/out",
    "content": "* inside\n* item\n"
  },
  {
    "path": "test/indent/markdown/inside-list-item/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/markdown.kak\"\nset buffer filetype markdown\n"
  },
  {
    "path": "test/indent/markdown/inside-nested-list-item/cmd",
    "content": "a<ret><esc>\n"
  },
  {
    "path": "test/indent/markdown/inside-nested-list-item/in",
    "content": "  - parent\n      * some%( )child\n"
  },
  {
    "path": "test/indent/markdown/inside-nested-list-item/out",
    "content": "  - parent\n      * some\n      * child\n"
  },
  {
    "path": "test/indent/markdown/inside-nested-list-item/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/markdown.kak\"\nset buffer filetype markdown\n"
  },
  {
    "path": "test/indent/markdown/inside-simple-blockquote/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/indent/markdown/inside-simple-blockquote/in",
    "content": "> block %(q)uote\n"
  },
  {
    "path": "test/indent/markdown/inside-simple-blockquote/out",
    "content": "> block\n> quote\n"
  },
  {
    "path": "test/indent/markdown/inside-simple-blockquote/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/markdown.kak\"\nset buffer filetype markdown\n"
  },
  {
    "path": "test/indent/markdown/inside-strong-emphasis/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/indent/markdown/inside-strong-emphasis/in",
    "content": "**strong %(e)mphasis**\n"
  },
  {
    "path": "test/indent/markdown/inside-strong-emphasis/out",
    "content": "**strong\nemphasis**\n"
  },
  {
    "path": "test/indent/markdown/inside-strong-emphasis/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/markdown.kak\"\nset buffer filetype markdown\n"
  },
  {
    "path": "test/indent/markdown/list-item/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/indent/markdown/list-item/in",
    "content": "%(*) list\n  - item\n"
  },
  {
    "path": "test/indent/markdown/list-item/out",
    "content": "\n* list\n  - item\n"
  },
  {
    "path": "test/indent/markdown/list-item/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/markdown.kak\"\nset buffer filetype markdown\n"
  },
  {
    "path": "test/indent/markdown/open-after-setext-heading-1/cmd",
    "content": "o\n"
  },
  {
    "path": "test/indent/markdown/open-after-setext-heading-1/in",
    "content": "Some Heading\n%(=)===========\n"
  },
  {
    "path": "test/indent/markdown/open-after-setext-heading-1/out",
    "content": "Some Heading\n============\n\n"
  },
  {
    "path": "test/indent/markdown/open-after-setext-heading-1/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/markdown.kak\"\nset buffer filetype markdown\n"
  },
  {
    "path": "test/indent/markdown/open-after-setext-heading-2/cmd",
    "content": "o\n"
  },
  {
    "path": "test/indent/markdown/open-after-setext-heading-2/in",
    "content": "Some Heading\n%(-)-----------\n"
  },
  {
    "path": "test/indent/markdown/open-after-setext-heading-2/out",
    "content": "Some Heading\n------------\n\n"
  },
  {
    "path": "test/indent/markdown/open-after-setext-heading-2/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/markdown.kak\"\nset buffer filetype markdown\n"
  },
  {
    "path": "test/indent/python/empty-start-of-file/cmd",
    "content": "gjA<ret>\n"
  },
  {
    "path": "test/indent/python/empty-start-of-file/in",
    "content": "#\n"
  },
  {
    "path": "test/indent/python/empty-start-of-file/out",
    "content": "#\n# \n"
  },
  {
    "path": "test/indent/python/empty-start-of-file/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/indent/python/empty-start-of-file-indented/cmd",
    "content": "gjA<ret>\n"
  },
  {
    "path": "test/indent/python/empty-start-of-file-indented/in",
    "content": "    #\n"
  },
  {
    "path": "test/indent/python/empty-start-of-file-indented/out",
    "content": "    #\n    # \n"
  },
  {
    "path": "test/indent/python/empty-start-of-file-indented/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/indent/python/exit-block/cmd",
    "content": "gjA<ret>\n"
  },
  {
    "path": "test/indent/python/exit-block/in",
    "content": "# A new line after a pair of empty comment lines should exit the\n# block comment.\n#\n#\n"
  },
  {
    "path": "test/indent/python/exit-block/out",
    "content": "# A new line after a pair of empty comment lines should exit the\n# block comment.\n\n"
  },
  {
    "path": "test/indent/python/exit-block/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/indent/python/exit-block-indented/cmd",
    "content": "gjA<ret>\n"
  },
  {
    "path": "test/indent/python/exit-block-indented/in",
    "content": "    # A new line after a pair of empty comment lines should exit the\n    # block comment.\n    #\n    #\n"
  },
  {
    "path": "test/indent/python/exit-block-indented/out",
    "content": "    # A new line after a pair of empty comment lines should exit the\n    # block comment.\n    \n"
  },
  {
    "path": "test/indent/python/exit-block-indented/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/indent/python/not-a-comment/cmd",
    "content": "gjA<ret>\n"
  },
  {
    "path": "test/indent/python/not-a-comment/in",
    "content": "# If we're not in a comment at all, make sure we do the right thing.\n# (two empty lines follow)\n\n\n"
  },
  {
    "path": "test/indent/python/not-a-comment/out",
    "content": "# If we're not in a comment at all, make sure we do the right thing.\n# (two empty lines follow)\n\n\n\n"
  },
  {
    "path": "test/indent/python/not-a-comment/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/indent/python/not-a-comment-indented/cmd",
    "content": "gjA<ret>\n"
  },
  {
    "path": "test/indent/python/not-a-comment-indented/in",
    "content": "    # If we're not in a comment at all, make sure we do the right thing.\n    # (one empty line and a 4-space prefixed line follows)\n\n    \n"
  },
  {
    "path": "test/indent/python/not-a-comment-indented/out",
    "content": "    # If we're not in a comment at all, make sure we do the right thing.\n    # (one empty line and a 4-space prefixed line follows)\n\n\n    \n"
  },
  {
    "path": "test/indent/python/not-a-comment-indented/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/indent/python/paragraph-break/cmd",
    "content": "gjA<ret>\n"
  },
  {
    "path": "test/indent/python/paragraph-break/in",
    "content": "# A new line after a single empty comment line should leave the empty comment\n# in place as a possible paragraph separator, starting a new comment with the\n# prefix copied.\n#\n"
  },
  {
    "path": "test/indent/python/paragraph-break/out",
    "content": "# A new line after a single empty comment line should leave the empty comment\n# in place as a possible paragraph separator, starting a new comment with the\n# prefix copied.\n#\n# \n"
  },
  {
    "path": "test/indent/python/paragraph-break/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/indent/python/paragraph-break-indented/cmd",
    "content": "gjA<ret>\n"
  },
  {
    "path": "test/indent/python/paragraph-break-indented/in",
    "content": "    # A new line after a single empty comment line should leave the comment in\n    # place as a possible paragraph separator.\n    #\n"
  },
  {
    "path": "test/indent/python/paragraph-break-indented/out",
    "content": "    # A new line after a single empty comment line should leave the comment in\n    # place as a possible paragraph separator.\n    #\n    # \n"
  },
  {
    "path": "test/indent/python/paragraph-break-indented/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/indent/ruby/deindent-on-if-else-end/cmd",
    "content": "c<ret>elsif<ret>else\n"
  },
  {
    "path": "test/indent/ruby/deindent-on-if-else-end/in",
    "content": "if%( )\n"
  },
  {
    "path": "test/indent/ruby/deindent-on-if-else-end/out",
    "content": "if\nelsif\nelse\nend\n"
  },
  {
    "path": "test/indent/ruby/deindent-on-if-else-end/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/ruby.kak\"\nset buffer filetype ruby\n"
  },
  {
    "path": "test/indent/ruby/do-keyword/cmd",
    "content": "c<ret><esc>jo<ret>e.map do |element|<ret><esc>\n"
  },
  {
    "path": "test/indent/ruby/do-keyword/in",
    "content": "test do%( )\n"
  },
  {
    "path": "test/indent/ruby/do-keyword/out",
    "content": "test do\n\nend\n\ne.map do |element|\n\nend\n"
  },
  {
    "path": "test/indent/ruby/do-keyword/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/ruby.kak\"\nset buffer filetype ruby\n"
  },
  {
    "path": "test/indent/ruby/following-blocks-should-not-prevent-end/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/indent/ruby/following-blocks-should-not-prevent-end/in",
    "content": "def foo%( )\n\ndef bar\nend\n"
  },
  {
    "path": "test/indent/ruby/following-blocks-should-not-prevent-end/out",
    "content": "def foo\n\nend\n\ndef bar\nend\n"
  },
  {
    "path": "test/indent/ruby/following-blocks-should-not-prevent-end/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/ruby.kak\"\nset buffer filetype ruby\n"
  },
  {
    "path": "test/indent/ruby/indent-after-class/cmd",
    "content": "c<ret><esc>O<esc>\n"
  },
  {
    "path": "test/indent/ruby/indent-after-class/in",
    "content": "class Test%( )\n"
  },
  {
    "path": "test/indent/ruby/indent-after-class/out",
    "content": "class Test\n\n\nend\n"
  },
  {
    "path": "test/indent/ruby/indent-after-class/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/ruby.kak\"\nset buffer filetype ruby\n"
  },
  {
    "path": "test/indent/ruby/insert-comment-hash/cmd",
    "content": "c<ret><esc><a-o>jo# comment3<ret>comment4<esc><a-o>jo#    indented<ret>indented2<esc><a-o>jodef f<ret># comment<ret>comment2<esc>j<a-o>jodef f<ret># comment<esc>hhhi<ret>\n"
  },
  {
    "path": "test/indent/ruby/insert-comment-hash/in",
    "content": "# Comment%( ) comment2\n"
  },
  {
    "path": "test/indent/ruby/insert-comment-hash/out",
    "content": "# Comment\n# comment2\n\n# comment3\n# comment4\n\n#    indented\n#    indented2\n\ndef f\n    # comment\n    # comment2\nend\n\ndef f\n    # comm\n    # ent\nend\n"
  },
  {
    "path": "test/indent/ruby/insert-comment-hash/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/ruby.kak\"\nset buffer filetype ruby\n"
  },
  {
    "path": "test/indent/ruby/method-definition-shouldnt-duplicate/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/indent/ruby/method-definition-shouldnt-duplicate/in",
    "content": "class Test\n    def test%( )\nend\n"
  },
  {
    "path": "test/indent/ruby/method-definition-shouldnt-duplicate/out",
    "content": "class Test\n    def test\n\n    end\nend\n"
  },
  {
    "path": "test/indent/ruby/method-definition-shouldnt-duplicate/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/ruby.kak\"\nset buffer filetype ruby\n"
  },
  {
    "path": "test/indent/rust/after-open/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/after-open/in",
    "content": "    Foo {%( )\n\n    foo(%( )\n\n    vec![%( )\n"
  },
  {
    "path": "test/indent/rust/after-open/out",
    "content": "    Foo {\n        bar\n\n    foo(\n        bar\n\n    vec![\n        bar\n"
  },
  {
    "path": "test/indent/rust/after-open/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/after-open-with-chars/cmd",
    "content": "c<ret>baz<esc>\n"
  },
  {
    "path": "test/indent/rust/after-open-with-chars/in",
    "content": "    Foo {bar,%( )\n\n    foo(bar,%( )\n\n    vec![bar,%( )\n"
  },
  {
    "path": "test/indent/rust/after-open-with-chars/out",
    "content": "    Foo {\n        bar,\n        baz\n\n    foo(\n        bar,\n        baz\n\n    vec![\n        bar,\n        baz\n"
  },
  {
    "path": "test/indent/rust/after-open-with-chars/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/after-variable/cmd",
    "content": "c<ret>.baz()<esc>\n"
  },
  {
    "path": "test/indent/rust/after-variable/in",
    "content": "    foo%( )\n\n    Foo(bar)%( )\n\n    Foo { bar }%( )\n\n"
  },
  {
    "path": "test/indent/rust/after-variable/out",
    "content": "    foo\n        .baz()\n\n    Foo(bar)\n        .baz()\n\n    Foo { bar }\n        .baz()\n\n"
  },
  {
    "path": "test/indent/rust/after-variable/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/after-where/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/after-where/in",
    "content": "    impl X for T\n    where%( )\n\n"
  },
  {
    "path": "test/indent/rust/after-where/out",
    "content": "    impl X for T\n    where\n        bar\n\n"
  },
  {
    "path": "test/indent/rust/after-where/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/align-closing-brace/cmd",
    "content": "c<ret>}<esc>\n"
  },
  {
    "path": "test/indent/rust/align-closing-brace/in",
    "content": "    Foo {\n        bar,\n        baz,%( )\n"
  },
  {
    "path": "test/indent/rust/align-closing-brace/out",
    "content": "    Foo {\n        bar,\n        baz,\n    }\n"
  },
  {
    "path": "test/indent/rust/align-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/align-closing-brack/cmd",
    "content": "c<ret>]<esc>\n"
  },
  {
    "path": "test/indent/rust/align-closing-brack/in",
    "content": "    vec![\n        bar,\n        baz,%( )\n"
  },
  {
    "path": "test/indent/rust/align-closing-brack/out",
    "content": "    vec![\n        bar,\n        baz,\n    ]\n"
  },
  {
    "path": "test/indent/rust/align-closing-brack/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/align-closing-paren/cmd",
    "content": "c<ret>)<esc>\n"
  },
  {
    "path": "test/indent/rust/align-closing-paren/in",
    "content": "    foo(\n        bar,\n        baz,%( )\n"
  },
  {
    "path": "test/indent/rust/align-closing-paren/out",
    "content": "    foo(\n        bar,\n        baz,\n    )\n"
  },
  {
    "path": "test/indent/rust/align-closing-paren/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/before-where/cmd",
    "content": "c<ret>where<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/before-where/in",
    "content": "    impl X for T%( )\n\n"
  },
  {
    "path": "test/indent/rust/before-where/out",
    "content": "    impl X for T\n    where\n        bar\n\n"
  },
  {
    "path": "test/indent/rust/before-where/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/block-comment/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/block-comment/in",
    "content": "    /* foo%( )\n\n    /*! foo%( )\n\n    /*!! foo%( )\n\n    /** foo%( )\n\n    /*** foo%( )\n\n    println!(\"hello world\"); /* foo%( )\n\n"
  },
  {
    "path": "test/indent/rust/block-comment/out",
    "content": "    /* foo\n     * bar\n\n    /*! foo\n     * bar\n\n    /*!! foo\n     * bar\n\n    /** foo\n     * bar\n\n    /*** foo\n     * bar\n\n    println!(\"hello world\"); /* foo\n    bar\n\n"
  },
  {
    "path": "test/indent/rust/block-comment/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/block-comment-close/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/block-comment-close/in",
    "content": "    /* foo\n     * %( )\n\n    /* foo\n     *%( )\n\n"
  },
  {
    "path": "test/indent/rust/block-comment-close/out",
    "content": "    /* foo\n     */\nbar\n\n    /* foo\n     *\n     * bar\n\n"
  },
  {
    "path": "test/indent/rust/block-comment-close/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/dedent/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/dedent/in",
    "content": "    foo();%( )\n\n    foo(\n        bar().baz(),%( )\n\n    foo(\n        bar()\n            .baz(),%( )\n\n    foo()\n        .bar()\n        .baz();%( )\n\n    let t = \"a\n    wah\";%( )\n\n"
  },
  {
    "path": "test/indent/rust/dedent/out",
    "content": "    foo();\n    bar\n\n    foo(\n        bar().baz(),\n        bar\n\n    foo(\n        bar()\n            .baz(),\n        bar\n\n    foo()\n        .bar()\n        .baz();\n    bar\n\n    let t = \"a\n    wah\";\n    bar\n\n"
  },
  {
    "path": "test/indent/rust/dedent/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/dedent-async/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/dedent-async/in",
    "content": "    foo()\n        .await%( )\n\n    foo().await%( )\n\n    foo()\n        .await?%( )\n\n"
  },
  {
    "path": "test/indent/rust/dedent-async/out",
    "content": "    foo()\n        .await\n        bar\n\n    foo().await\n        bar\n\n    foo()\n        .await?\n        bar\n\n"
  },
  {
    "path": "test/indent/rust/dedent-async/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/dedent-function-closing-brace/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/rust/dedent-function-closing-brace/in",
    "content": "fn foo() -> i32 {%( )}\n\nfn foo() -> i32 {%( )\n}\n\nfn foo() -> i32 {%( )bar()}\n\nfn foo() -> i32 {%( )bar()\n}\n\nfn foo() -> i32 {\n    bar()%( )}\n\n    fn foo() -> i32 {\n        bar()%( )}\n\n    fn foo() -> i32 {\n        bar()?%( )}\n\n"
  },
  {
    "path": "test/indent/rust/dedent-function-closing-brace/out",
    "content": "fn foo() -> i32 {\n}\n\nfn foo() -> i32 {\n    \n}\n\nfn foo() -> i32 {\n    bar()}\n\nfn foo() -> i32 {\n    bar()\n}\n\nfn foo() -> i32 {\n    bar()\n}\n\n    fn foo() -> i32 {\n        bar()\n    }\n\n    fn foo() -> i32 {\n        bar()?\n    }\n\n"
  },
  {
    "path": "test/indent/rust/dedent-function-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/dedent-generic-closing-brace/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/rust/dedent-generic-closing-brace/in",
    "content": "{%( )}\n\n{%( )\n}\n\n{%( )bar()}\n\n{%( )bar()\n}\n\n{\n    bar()%( )}\n\n      {\n   bar()%( )}\n\n{(%( ))}\n\n{(%( )\n)}\n\n{(%( )foo())}\n\n{(%( )foo()\n)}\n\n{(\n    bar()%( ))}\n\n{(\n    bar()?%( ))}\n\n   {(\n                        bar()%( ))}\n"
  },
  {
    "path": "test/indent/rust/dedent-generic-closing-brace/out",
    "content": "{\n}\n\n{\n    \n}\n\n{\n    bar()}\n\n{\n    bar()\n}\n\n{\n    bar()\n}\n\n      {\n   bar()\n      }\n\n{(\n)}\n\n{(\n    \n)}\n\n{(\n    foo())}\n\n{(\n    foo()\n)}\n\n{(\n    bar()\n)}\n\n{(\n    bar()?\n)}\n\n   {(\n                        bar()\n   )}\n"
  },
  {
    "path": "test/indent/rust/dedent-generic-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/dedent-if-closing-brace/cmd",
    "content": "c<ret>\n"
  },
  {
    "path": "test/indent/rust/dedent-if-closing-brace/in",
    "content": "if true {%( )}\n\nif true {%( )\n}\n\nif true {%( )bar()}\n\nif true {%( )bar()\n}\n\nif true {\n    bar()%( )}\n\n  if true {\n            bar()%( )}\n"
  },
  {
    "path": "test/indent/rust/dedent-if-closing-brace/out",
    "content": "if true {\n}\n\nif true {\n    \n}\n\nif true {\n    bar()}\n\nif true {\n    bar()\n}\n\nif true {\n    bar()\n}\n\n  if true {\n            bar()\n  }\n"
  },
  {
    "path": "test/indent/rust/dedent-if-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/empty-line/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/empty-line/in",
    "content": "    %( )\n\n"
  },
  {
    "path": "test/indent/rust/empty-line/out",
    "content": "\n    bar\n\n"
  },
  {
    "path": "test/indent/rust/empty-line/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/empty-match/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/empty-match/in",
    "content": "match a {\n    Some(a) => {}%( )\n\n"
  },
  {
    "path": "test/indent/rust/empty-match/out",
    "content": "match a {\n    Some(a) => {}\n    bar\n\n"
  },
  {
    "path": "test/indent/rust/empty-match/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/line-comment/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/line-comment/in",
    "content": "    // foo%( )\n\n    //! foo%( )\n\n    //!! foo%( )\n\n    /// foo%( )\n\n    //// foo%( )\n\n    println!(\"hello world\"); // foo%( )\n\n"
  },
  {
    "path": "test/indent/rust/line-comment/out",
    "content": "    // foo\n    // bar\n\n    //! foo\n    //! bar\n\n    //!! foo\n    //!! bar\n\n    /// foo\n    /// bar\n\n    //// foo\n    //// bar\n\n    println!(\"hello world\"); // foo\n    bar\n\n"
  },
  {
    "path": "test/indent/rust/line-comment/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/line-comment-close/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/line-comment-close/in",
    "content": "    // foo\n    // %( )\n\n    // foo\n    //%( )\n\n    //! %( )\n\n    //!%( )\n\n    //!! %( )\n\n    //!!%( )\n\n    /// %( )\n\n    ///%( )\n\n    //// %( )\n\n    ////%( )\n\n    println!(\"hello world\"); // %( )\n\n    println!(\"hello world\"); //%( )\n\n"
  },
  {
    "path": "test/indent/rust/line-comment-close/out",
    "content": "    // foo\n    //\n    // bar\n\n    // foo\n    bar\n\n    //!\n    //! bar\n\n    bar\n\n    //!!\n    //!! bar\n\n    bar\n\n    ///\n    /// bar\n\n    bar\n\n    ////\n    //// bar\n\n    bar\n\n    println!(\"hello world\"); //\n    bar\n\n    println!(\"hello world\"); //\n    bar\n\n"
  },
  {
    "path": "test/indent/rust/line-comment-close/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/line-start-with-operator/bar",
    "content": "    .foo\n    bar\n\n    + foo\n    bar\n\n    - foo\n    bar\n\n    * foo\n    bar\n\n    / foo\n    bar\n\n    & foo\n    bar\n\n    | foo\n    bar\n\n    ^ foo\n    bar\n\n    && foo\n    bar\n\n    || foo\n    bar\n\n    < foo\n    bar\n\n    > foo\n    bar\n\n"
  },
  {
    "path": "test/indent/rust/line-start-with-operator/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/rust/line-start-with-operator/in",
    "content": "    .foo%( )\n\n    .foo()%( )\n\n    + foo%( )\n\n    - foo%( )\n\n    * foo%( )\n\n    / foo%( )\n\n    & foo%( )\n\n    | foo%( )\n\n    ^ foo%( )\n\n    && foo%( )\n\n    || foo%( )\n\n    < foo%( )\n\n    > foo%( )\n\n    #[derive(Debug)]%( )\n\n    fn foo(\n    ) -> {%( )\n\n    } else {%( )\n\n"
  },
  {
    "path": "test/indent/rust/line-start-with-operator/out",
    "content": "    .foo\n        bar\n\n    .foo()\n    bar\n\n    + foo\n    bar\n\n    - foo\n    bar\n\n    * foo\n    bar\n\n    / foo\n    bar\n\n    & foo\n    bar\n\n    | foo\n    bar\n\n    ^ foo\n    bar\n\n    && foo\n    bar\n\n    || foo\n    bar\n\n    < foo\n    bar\n\n    > foo\n    bar\n\n    #[derive(Debug)]\n    bar\n\n    fn foo(\n    ) -> {\n        bar\n\n    } else {\n        bar\n\n"
  },
  {
    "path": "test/indent/rust/line-start-with-operator/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/rust/on-open-paren/cmd",
    "content": "c{<esc>\n"
  },
  {
    "path": "test/indent/rust/on-open-paren/in",
    "content": "    fn foo<T>(x: T)\n    where\n        T: Debug\n        %( )\n\n    pub async fn foo<T>(x: T)\n    where\n        T: Debug\n        %( )\n\n    pub(in std::fmt) const unsafe extern \"C\" fn foo<T>(x: T)\n    where\n        T: Debug\n        %( )\n\n    fn foo<T>(x: T) where T: Debug %( )\n\n    impl<T> X for T\n    where\n        T: Debug\n        %( )\n\n    impl<T> X for T where T: Debug %( )\n\n    struct X<T>\n    where\n        T: Debug\n        %( )\n\n    pub(crate) struct X<T>\n    where\n        T: Debug\n        %( )\n\n    struct X<T> where T: Debug %( )\n\n    enum X<T>\n    where\n        T: Debug\n        %( )\n\n    enum X<T> where T: Debug %( )\n\n    union X<T>\n    where\n        T: Debug\n        %( )\n\n    union X<T> where T: Debug %( )\n\n    if foo()\n        && bar()\n        %( )\n\n    for x in group\n        .iter()\n        .sorted_by(|a, b| Ord::cmp(&a.0, &b.0))\n        %( )\n\n"
  },
  {
    "path": "test/indent/rust/on-open-paren/out",
    "content": "    fn foo<T>(x: T)\n    where\n        T: Debug\n    {\n\n    pub async fn foo<T>(x: T)\n    where\n        T: Debug\n    {\n\n    pub(in std::fmt) const unsafe extern \"C\" fn foo<T>(x: T)\n    where\n        T: Debug\n    {\n\n    fn foo<T>(x: T) where T: Debug {\n\n    impl<T> X for T\n    where\n        T: Debug\n    {\n\n    impl<T> X for T where T: Debug {\n\n    struct X<T>\n    where\n        T: Debug\n    {\n\n    pub(crate) struct X<T>\n    where\n        T: Debug\n    {\n\n    struct X<T> where T: Debug {\n\n    enum X<T>\n    where\n        T: Debug\n    {\n\n    enum X<T> where T: Debug {\n\n    union X<T>\n    where\n        T: Debug\n    {\n\n    union X<T> where T: Debug {\n\n    if foo()\n        && bar()\n    {\n\n    for x in group\n        .iter()\n        .sorted_by(|a, b| Ord::cmp(&a.0, &b.0))\n    {\n\n"
  },
  {
    "path": "test/indent/rust/on-open-paren/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/rust.kak\"\nset buffer filetype rust\n"
  },
  {
    "path": "test/indent/sh/deindent-after-done/cmd",
    "content": "gei<ret>\n"
  },
  {
    "path": "test/indent/sh/deindent-after-done/in",
    "content": "while true; do\n    thing1\n    done\n"
  },
  {
    "path": "test/indent/sh/deindent-after-done/out",
    "content": "while true; do\n    thing1\ndone\n\n"
  },
  {
    "path": "test/indent/sh/deindent-after-done/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/deindent-after-esac/cmd",
    "content": "gei<ret>\n"
  },
  {
    "path": "test/indent/sh/deindent-after-esac/in",
    "content": "case $foo in\n    bar)\n        thing1\n        thing2\n    ;;\n    esac\n"
  },
  {
    "path": "test/indent/sh/deindent-after-esac/out",
    "content": "case $foo in\n    bar)\n        thing1\n        thing2\n    ;;\nesac\n\n"
  },
  {
    "path": "test/indent/sh/deindent-after-esac/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/deindent-after-fi/cmd",
    "content": "gei<ret>fi<ret>\n"
  },
  {
    "path": "test/indent/sh/deindent-after-fi/in",
    "content": "if [ $foo ]; then\n    thing1\n"
  },
  {
    "path": "test/indent/sh/deindent-after-fi/out",
    "content": "if [ $foo ]; then\n    thing1\nfi\n\n"
  },
  {
    "path": "test/indent/sh/deindent-after-fi/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/deindent-case-option/cmd",
    "content": "gei<ret>;;<ret>*)\n"
  },
  {
    "path": "test/indent/sh/deindent-case-option/in",
    "content": "case $foo in\n    bar) thing1;;\n    baz)\n        thing1\n        thing2\n"
  },
  {
    "path": "test/indent/sh/deindent-case-option/out",
    "content": "case $foo in\n    bar) thing1;;\n    baz)\n        thing1\n        thing2\n        ;;\n    *)\n"
  },
  {
    "path": "test/indent/sh/deindent-case-option/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/deindent-compound-command/cmd",
    "content": "gei<ret>\n"
  },
  {
    "path": "test/indent/sh/deindent-compound-command/in",
    "content": "foo () {\n    thing1\n    }\n"
  },
  {
    "path": "test/indent/sh/deindent-compound-command/out",
    "content": "foo () {\n    thing1\n}\n\n"
  },
  {
    "path": "test/indent/sh/deindent-compound-command/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/deindent-one-line-case-option/cmd",
    "content": "gei<ret>baz)\n"
  },
  {
    "path": "test/indent/sh/deindent-one-line-case-option/in",
    "content": "case $foo in\n    bar) thing1;;\n"
  },
  {
    "path": "test/indent/sh/deindent-one-line-case-option/out",
    "content": "case $foo in\n    bar) thing1;;\n    baz)\n"
  },
  {
    "path": "test/indent/sh/deindent-one-line-case-option/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/deindent-only-else/cmd",
    "content": "gei<ret>baz\n"
  },
  {
    "path": "test/indent/sh/deindent-only-else/in",
    "content": "if [ $foo ]; then\n    bar\n    else\n"
  },
  {
    "path": "test/indent/sh/deindent-only-else/out",
    "content": "if [ $foo ]; then\n    bar\nelse\n    baz\n"
  },
  {
    "path": "test/indent/sh/deindent-only-else/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/indent-after-do/cmd",
    "content": "gei<ret>thing1\n"
  },
  {
    "path": "test/indent/sh/indent-after-do/in",
    "content": "while true; do\n"
  },
  {
    "path": "test/indent/sh/indent-after-do/out",
    "content": "while true; do\n    thing1\n"
  },
  {
    "path": "test/indent/sh/indent-after-do/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/indent-after-in/cmd",
    "content": "gei<ret>bar)\n"
  },
  {
    "path": "test/indent/sh/indent-after-in/in",
    "content": "case $foo in\n"
  },
  {
    "path": "test/indent/sh/indent-after-in/out",
    "content": "case $foo in\n    bar)\n"
  },
  {
    "path": "test/indent/sh/indent-after-in/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/indent-after-then/cmd",
    "content": "gei<ret>thing1\n"
  },
  {
    "path": "test/indent/sh/indent-after-then/in",
    "content": "if [ $foo ]; then\n"
  },
  {
    "path": "test/indent/sh/indent-after-then/out",
    "content": "if [ $foo ]; then\n    thing1\n"
  },
  {
    "path": "test/indent/sh/indent-after-then/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/indent-block-case/cmd",
    "content": "gei<ret>thing1<ret>thing2\n"
  },
  {
    "path": "test/indent/sh/indent-block-case/in",
    "content": "case $foo in\n    bar) thing1;;\n    baz)\n"
  },
  {
    "path": "test/indent/sh/indent-block-case/out",
    "content": "case $foo in\n    bar) thing1;;\n    baz)\n        thing1\n        thing2\n"
  },
  {
    "path": "test/indent/sh/indent-block-case/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/indent-compound-command/cmd",
    "content": "gei<ret>thing1\n"
  },
  {
    "path": "test/indent/sh/indent-compound-command/in",
    "content": "foo () {\n"
  },
  {
    "path": "test/indent/sh/indent-compound-command/out",
    "content": "foo () {\n    thing1\n"
  },
  {
    "path": "test/indent/sh/indent-compound-command/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/no-deindent-after-else/cmd",
    "content": "gk3jobaz\n"
  },
  {
    "path": "test/indent/sh/no-deindent-after-else/in",
    "content": "if [ $foo ]; then\n    if [ $bar ]; then\n        foobar\n    else\n        qux\n    fi\nfi\n"
  },
  {
    "path": "test/indent/sh/no-deindent-after-else/out",
    "content": "if [ $foo ]; then\n    if [ $bar ]; then\n        foobar\n    else\n        baz\n        qux\n    fi\nfi\n"
  },
  {
    "path": "test/indent/sh/no-deindent-after-else/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/sh/no-deindent-after-fi/cmd",
    "content": "gk5jobaz\n"
  },
  {
    "path": "test/indent/sh/no-deindent-after-fi/in",
    "content": "if [ $foo ]; then\n    if [ $bar ]; then\n        foobar\n    else\n        qux\n    fi\nfi\n"
  },
  {
    "path": "test/indent/sh/no-deindent-after-fi/out",
    "content": "if [ $foo ]; then\n    if [ $bar ]; then\n        foobar\n    else\n        qux\n    fi\n    baz\nfi\n"
  },
  {
    "path": "test/indent/sh/no-deindent-after-fi/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/sh.kak\"\nset buffer filetype sh\n"
  },
  {
    "path": "test/indent/swift/after-open-brace/cmd",
    "content": "c<ret>bar<esc>\n"
  },
  {
    "path": "test/indent/swift/after-open-brace/in",
    "content": "func foo() {%( )\n"
  },
  {
    "path": "test/indent/swift/after-open-brace/out",
    "content": "func foo() {\n    bar\n"
  },
  {
    "path": "test/indent/swift/after-open-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/swift.kak\"\nset buffer filetype swift\n"
  },
  {
    "path": "test/indent/swift/align-closing-brace/cmd",
    "content": "c<ret>}<esc>\n"
  },
  {
    "path": "test/indent/swift/align-closing-brace/in",
    "content": "func foo() {\n    bar%( )\n"
  },
  {
    "path": "test/indent/swift/align-closing-brace/out",
    "content": "func foo() {\n    bar\n}\n"
  },
  {
    "path": "test/indent/swift/align-closing-brace/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/swift.kak\"\nset buffer filetype swift\n"
  },
  {
    "path": "test/indent/swift/closure-after-in/cmd",
    "content": "c<ret>return item * 2<esc>\n"
  },
  {
    "path": "test/indent/swift/closure-after-in/in",
    "content": "array.map { item in%( )\n"
  },
  {
    "path": "test/indent/swift/closure-after-in/out",
    "content": "array.map { item in\n    return item * 2\n"
  },
  {
    "path": "test/indent/swift/closure-after-in/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/swift.kak\"\nset buffer filetype swift\n"
  },
  {
    "path": "test/indent/swift/comment-continuation/cmd",
    "content": "c<ret>second line<esc>\n"
  },
  {
    "path": "test/indent/swift/comment-continuation/in",
    "content": "// This is a comment%( )\n"
  },
  {
    "path": "test/indent/swift/comment-continuation/out",
    "content": "// This is a comment\n// second line\n"
  },
  {
    "path": "test/indent/swift/comment-continuation/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/swift.kak\"\nset buffer filetype swift\n"
  },
  {
    "path": "test/indent/swift/doc-comment-continuation/cmd",
    "content": "c<ret>second line<esc>\n"
  },
  {
    "path": "test/indent/swift/doc-comment-continuation/in",
    "content": "/// Documentation comment%( )\n"
  },
  {
    "path": "test/indent/swift/doc-comment-continuation/out",
    "content": "/// Documentation comment\n/// second line\n"
  },
  {
    "path": "test/indent/swift/doc-comment-continuation/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/swift.kak\"\nset buffer filetype swift\n"
  },
  {
    "path": "test/normal/align/cmd",
    "content": "&\n"
  },
  {
    "path": "test/normal/align/in",
    "content": "a %(a)\nbb %(b)b\nccc %(c)cc\n"
  },
  {
    "path": "test/normal/align/out",
    "content": "a   a\nbb  bb\nccc ccc\n"
  },
  {
    "path": "test/normal/align-tab/cmd",
    "content": "&\n"
  },
  {
    "path": "test/normal/align-tab/in",
    "content": "\t\t\t\tif (%(v)alid)\n\t\t\t\t%(x)\n"
  },
  {
    "path": "test/normal/align-tab/out",
    "content": "\t\t\t\tif (valid)\n\t\t\t\t\tx\n"
  },
  {
    "path": "test/normal/align-tab/rc",
    "content": "set buffer tabstop 4\nset buffer aligntab true\n"
  },
  {
    "path": "test/normal/all/cmd",
    "content": "%\n"
  },
  {
    "path": "test/normal/all/in",
    "content": "a\nb\nc\nd\n"
  },
  {
    "path": "test/normal/all/kak_quoted_selections",
    "content": "'a\nb\nc\nd\n'\n"
  },
  {
    "path": "test/normal/append/cmd",
    "content": "append\n"
  },
  {
    "path": "test/normal/append/in",
    "content": "a\n"
  },
  {
    "path": "test/normal/append/kak_quoted_selections",
    "content": "'append\n'\n"
  },
  {
    "path": "test/normal/append-at-eol/cmd",
    "content": "APPEND\n"
  },
  {
    "path": "test/normal/append-at-eol/in",
    "content": "A\n"
  },
  {
    "path": "test/normal/append-at-eol/out",
    "content": "APPEND\n"
  },
  {
    "path": "test/normal/change/cmd",
    "content": "cfacile\n"
  },
  {
    "path": "test/normal/change/in",
    "content": "c’est %(difficile).\n"
  },
  {
    "path": "test/normal/change/out",
    "content": "c’est facile.\n"
  },
  {
    "path": "test/normal/clear-selections/cmd",
    "content": ";\n"
  },
  {
    "path": "test/normal/clear-selections/in",
    "content": "%(foo)\n"
  },
  {
    "path": "test/normal/clear-selections/kak_quoted_selections",
    "content": "'o'\n"
  },
  {
    "path": "test/normal/codepoint-width/tab-width/cmd",
    "content": "j\n"
  },
  {
    "path": "test/normal/codepoint-width/tab-width/enabled",
    "content": "#!/bin/sh\nlocale | grep LC_CTYPE | grep -qi 'utf-*8'\n"
  },
  {
    "path": "test/normal/codepoint-width/tab-width/in",
    "content": "一\t%(二)\n1234567890\n"
  },
  {
    "path": "test/normal/codepoint-width/tab-width/kak_quoted_selections",
    "content": "'9'\n"
  },
  {
    "path": "test/normal/codepoint-width/vertical-movement/cmd",
    "content": "k\n"
  },
  {
    "path": "test/normal/codepoint-width/vertical-movement/enabled",
    "content": "#!/bin/sh\nlocale | grep LC_CTYPE | grep -qi 'utf-*8'\n"
  },
  {
    "path": "test/normal/codepoint-width/vertical-movement/in",
    "content": "123456789012\n一%(二)三%(四)五六\n"
  },
  {
    "path": "test/normal/codepoint-width/vertical-movement/kak_quoted_selections",
    "content": "'3' '7'\n"
  },
  {
    "path": "test/normal/concat-paste-line/cmd",
    "content": "xy<a-p>\n"
  },
  {
    "path": "test/normal/concat-paste-line/in",
    "content": "line\n"
  },
  {
    "path": "test/normal/concat-paste-line/kak_selections_desc",
    "content": "2.1,2.5\n"
  },
  {
    "path": "test/normal/concat-paste-line/out",
    "content": "line\nline\n"
  },
  {
    "path": "test/normal/convert-spaces-to-tabs/cmd",
    "content": "%<a-@>\n"
  },
  {
    "path": "test/normal/convert-spaces-to-tabs/in",
    "content": "void main()\n{\n        foo();\n}\n"
  },
  {
    "path": "test/normal/convert-spaces-to-tabs/out",
    "content": "void main()\n{\n\tfoo();\n}\n"
  },
  {
    "path": "test/normal/convert-tabs-to-spaces/cmd",
    "content": "%@\n"
  },
  {
    "path": "test/normal/convert-tabs-to-spaces/in",
    "content": "void main()\n{\n\tfoo();\n}\n"
  },
  {
    "path": "test/normal/convert-tabs-to-spaces/out",
    "content": "void main()\n{\n        foo();\n}\n"
  },
  {
    "path": "test/normal/copy-sel-above/cmd",
    "content": "2<a-C>\n"
  },
  {
    "path": "test/normal/copy-sel-above/in",
    "content": "foo\nbar\n%(baz)\n"
  },
  {
    "path": "test/normal/copy-sel-above/kak_quoted_selections",
    "content": "'foo' 'bar' 'baz'\n"
  },
  {
    "path": "test/normal/copy-sel-below/cmd",
    "content": "2C\n"
  },
  {
    "path": "test/normal/copy-sel-below/in",
    "content": "%(foo)\nbar\nbaz\n"
  },
  {
    "path": "test/normal/copy-sel-below/kak_quoted_selections",
    "content": "'foo' 'bar' 'baz'\n"
  },
  {
    "path": "test/normal/copy-sel-below-multi-line/cmd",
    "content": "2C\n"
  },
  {
    "path": "test/normal/copy-sel-below-multi-line/in",
    "content": "%(foo\nbar)\nbar\nbaz\nqux\nfoo\n"
  },
  {
    "path": "test/normal/copy-sel-below-multi-line/kak_quoted_selections",
    "content": "'foo\nbar' 'bar\nbaz' 'qux\nfoo'\n"
  },
  {
    "path": "test/normal/deindent/cmd",
    "content": "<\n"
  },
  {
    "path": "test/normal/deindent/in",
    "content": "    foo\n"
  },
  {
    "path": "test/normal/deindent/out",
    "content": "foo\n"
  },
  {
    "path": "test/normal/delete/cmd",
    "content": "d\n"
  },
  {
    "path": "test/normal/delete/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/delete/out",
    "content": "foo  baz\n"
  },
  {
    "path": "test/normal/duplicate-selections/cmd",
    "content": "++ao<esc>\n"
  },
  {
    "path": "test/normal/duplicate-selections/in",
    "content": "%(f) %(b) %(t)\n"
  },
  {
    "path": "test/normal/duplicate-selections/out",
    "content": "fooo booo tooo\n"
  },
  {
    "path": "test/normal/extend-lines/cmd",
    "content": "x\n"
  },
  {
    "path": "test/normal/extend-lines/in",
    "content": "one %(line\ntwo lines\nthree) lines\n"
  },
  {
    "path": "test/normal/extend-lines/kak_quoted_selections",
    "content": "'one line\ntwo lines\nthree lines\n'\n"
  },
  {
    "path": "test/normal/extra-word-chars/cmd",
    "content": "ww\n"
  },
  {
    "path": "test/normal/extra-word-chars/in",
    "content": "a-word another'one\n"
  },
  {
    "path": "test/normal/extra-word-chars/kak_quoted_selections",
    "content": "'another'\\''one'\n"
  },
  {
    "path": "test/normal/extra-word-chars/rc",
    "content": "set buffer extra_word_chars '-' \"'\"\n"
  },
  {
    "path": "test/normal/facedesc-invalid-syntax/attributes/cmd",
    "content": ":face global Default default,default+<ret>\n"
  },
  {
    "path": "test/normal/facedesc-invalid-syntax/attributes/error",
    "content": "'exec': 1:1: 'face': invalid face description, expected [<fg>][,<bg>[,<underline>]][+<attr>][@base] or just [base]\n"
  },
  {
    "path": "test/normal/facedesc-invalid-syntax/bg-color/cmd",
    "content": ":face global Default default,<ret>\n"
  },
  {
    "path": "test/normal/facedesc-invalid-syntax/bg-color/error",
    "content": "'exec': 1:1: 'face': invalid face description, expected [<fg>][,<bg>[,<underline>]][+<attr>][@base] or just [base]\n"
  },
  {
    "path": "test/normal/find-char-backward/cmd",
    "content": "<a-f>|\n"
  },
  {
    "path": "test/normal/find-char-backward/in",
    "content": "foo|%(bar)|baz\n"
  },
  {
    "path": "test/normal/find-char-backward/kak_quoted_selections",
    "content": "'|bar'\n"
  },
  {
    "path": "test/normal/find-char-backward-extending/cmd",
    "content": "<a-F>|\n"
  },
  {
    "path": "test/normal/find-char-backward-extending/in",
    "content": "foo|%(bar)|baz\n"
  },
  {
    "path": "test/normal/find-char-backward-extending/kak_quoted_selections",
    "content": "'|b'\n"
  },
  {
    "path": "test/normal/find-char-forward/cmd",
    "content": "f|\n"
  },
  {
    "path": "test/normal/find-char-forward/in",
    "content": "foo|%(bar)|baz\n"
  },
  {
    "path": "test/normal/find-char-forward/kak_quoted_selections",
    "content": "'r|'\n"
  },
  {
    "path": "test/normal/find-char-forward-extending/cmd",
    "content": "F|\n"
  },
  {
    "path": "test/normal/find-char-forward-extending/in",
    "content": "foo|%(bar)|baz\n"
  },
  {
    "path": "test/normal/find-char-forward-extending/kak_quoted_selections",
    "content": "'bar|'\n"
  },
  {
    "path": "test/normal/goto/buffer-bottom/cmd",
    "content": "gj\n"
  },
  {
    "path": "test/normal/goto/buffer-bottom/in",
    "content": "foo\n%(bar)\nbaz\n"
  },
  {
    "path": "test/normal/goto/buffer-bottom/kak_quoted_selections",
    "content": "'b'\n"
  },
  {
    "path": "test/normal/goto/buffer-end/cmd",
    "content": "ge\n"
  },
  {
    "path": "test/normal/goto/buffer-end/in",
    "content": "foo\n%(bar)\nbaz\n"
  },
  {
    "path": "test/normal/goto/buffer-end/kak_quoted_selections",
    "content": "'\n'\n"
  },
  {
    "path": "test/normal/goto/buffer-top/cmd",
    "content": "gk\n"
  },
  {
    "path": "test/normal/goto/buffer-top/in",
    "content": "foo\n%(bar)\nbaz\n"
  },
  {
    "path": "test/normal/goto/buffer-top/kak_quoted_selections",
    "content": "'f'\n"
  },
  {
    "path": "test/normal/goto/file/cmd",
    "content": "gf\n"
  },
  {
    "path": "test/normal/goto/file/in",
    "content": "%(goto-file)\n"
  },
  {
    "path": "test/normal/goto/file/out",
    "content": "pass\n"
  },
  {
    "path": "test/normal/goto/file/rc",
    "content": "nop %sh{ printf 'pass\\n' > goto-file }\n"
  },
  {
    "path": "test/normal/goto/last-buffer/cmd",
    "content": "ga\n"
  },
  {
    "path": "test/normal/goto/last-buffer/out",
    "content": "last-buffer\n"
  },
  {
    "path": "test/normal/goto/last-buffer/rc",
    "content": "nop %sh{ printf 'last-buffer\\n' > last-buffer }\n\nedit last-buffer\nbuffer out\n"
  },
  {
    "path": "test/normal/goto/last-change/cmd",
    "content": "dggg.\n"
  },
  {
    "path": "test/normal/goto/last-change/in",
    "content": "foo\n%(bar)\nbaz\n"
  },
  {
    "path": "test/normal/goto/last-change/kak_quoted_selections",
    "content": "'\n'\n"
  },
  {
    "path": "test/normal/goto/line-begin/cmd",
    "content": "gh\n"
  },
  {
    "path": "test/normal/goto/line-begin/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/goto/line-begin/kak_quoted_selections",
    "content": "'f'\n"
  },
  {
    "path": "test/normal/goto/line-end/cmd",
    "content": "gl\n"
  },
  {
    "path": "test/normal/goto/line-end/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/goto/line-end/kak_quoted_selections",
    "content": "'z'\n"
  },
  {
    "path": "test/normal/goto-extending/buffer-bottom/cmd",
    "content": "Gj\n"
  },
  {
    "path": "test/normal/goto-extending/buffer-bottom/in",
    "content": "foo\n%(bar)\nbaz\n"
  },
  {
    "path": "test/normal/goto-extending/buffer-bottom/kak_quoted_selections",
    "content": "'bar\nb'\n"
  },
  {
    "path": "test/normal/goto-extending/buffer-end/cmd",
    "content": "Ge\n"
  },
  {
    "path": "test/normal/goto-extending/buffer-end/in",
    "content": "foo\n%(bar)\nbaz\n"
  },
  {
    "path": "test/normal/goto-extending/buffer-end/kak_quoted_selections",
    "content": "'bar\nbaz\n'\n"
  },
  {
    "path": "test/normal/goto-extending/buffer-top/cmd",
    "content": "Gk\n"
  },
  {
    "path": "test/normal/goto-extending/buffer-top/in",
    "content": "foo\n%(bar)\nbaz\n"
  },
  {
    "path": "test/normal/goto-extending/buffer-top/kak_quoted_selections",
    "content": "'foo\nb'\n"
  },
  {
    "path": "test/normal/goto-extending/last-change/cmd",
    "content": "dggG.\n"
  },
  {
    "path": "test/normal/goto-extending/last-change/in",
    "content": "foo\n%(bar)\nbaz\n"
  },
  {
    "path": "test/normal/goto-extending/last-change/kak_quoted_selections",
    "content": "'foo\n\n'\n"
  },
  {
    "path": "test/normal/goto-extending/line-begin/cmd",
    "content": "Gh\n"
  },
  {
    "path": "test/normal/goto-extending/line-begin/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/goto-extending/line-begin/kak_quoted_selections",
    "content": "'foo b'\n"
  },
  {
    "path": "test/normal/goto-extending/line-end/cmd",
    "content": "Gl\n"
  },
  {
    "path": "test/normal/goto-extending/line-end/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/goto-extending/line-end/kak_quoted_selections",
    "content": "'bar baz'\n"
  },
  {
    "path": "test/normal/indent/cmd",
    "content": "<\n"
  },
  {
    "path": "test/normal/indent/in",
    "content": "    %(foo)\n"
  },
  {
    "path": "test/normal/indent/out",
    "content": "foo\n"
  },
  {
    "path": "test/normal/insert/cmd",
    "content": "iinser\n"
  },
  {
    "path": "test/normal/insert/in",
    "content": "t\n"
  },
  {
    "path": "test/normal/insert/out",
    "content": "insert\n"
  },
  {
    "path": "test/normal/insert-at-line-start/cmd",
    "content": "IINSER\n"
  },
  {
    "path": "test/normal/insert-at-line-start/in",
    "content": "T\n"
  },
  {
    "path": "test/normal/insert-at-line-start/out",
    "content": "INSERT\n"
  },
  {
    "path": "test/normal/insert-replace/cmd",
    "content": "cthis was <c-r>\" <esc>\n"
  },
  {
    "path": "test/normal/insert-replace/in",
    "content": "%(word1)%(word2)%(word3)%(word4)\n"
  },
  {
    "path": "test/normal/insert-replace/out",
    "content": "this was word1 this was word2 this was word3 this was word4 \n"
  },
  {
    "path": "test/normal/jump/backward/cmd",
    "content": "gj\n/bar<ret>\n/qux<ret>\n<c-o><c-o><c-o>\naend<esc>\n"
  },
  {
    "path": "test/normal/jump/backward/in",
    "content": "%(foo)\nbar\nqux\n"
  },
  {
    "path": "test/normal/jump/backward/out",
    "content": "fooend\nbar\nqux\n"
  },
  {
    "path": "test/normal/jump/backward-count/cmd",
    "content": "gj\n/bar<ret>\n/qux<ret>\n3<c-o>\naend<esc>\n"
  },
  {
    "path": "test/normal/jump/backward-count/in",
    "content": "%(foo)\nbar\nqux\n"
  },
  {
    "path": "test/normal/jump/backward-count/out",
    "content": "fooend\nbar\nqux\n"
  },
  {
    "path": "test/normal/jump/backward-dirty-middle/cmd",
    "content": "gj\n/bar<ret>\n/qux<ret>\n<c-o>\nh\n<c-o>\naend<esc>\n"
  },
  {
    "path": "test/normal/jump/backward-dirty-middle/in",
    "content": "%(foo)\nbar\nqux\n"
  },
  {
    "path": "test/normal/jump/backward-dirty-middle/out",
    "content": "foo\nbarend\nqux\n"
  },
  {
    "path": "test/normal/jump/backward-invalid-count/cmd",
    "content": "gj\n/bar<ret>\n/qux<ret>\n42<c-o>\n"
  },
  {
    "path": "test/normal/jump/backward-invalid-count/error",
    "content": "'exec': no previous jump\n"
  },
  {
    "path": "test/normal/jump/backward-invalid-count/in",
    "content": "%(foo)\nbar\nqux\n"
  },
  {
    "path": "test/normal/jump/forward/cmd",
    "content": "gj\n/bar<ret>\n/qux<ret>\n<c-o><c-o><c-o>\n<tab><tab><tab>\naend<esc>\n"
  },
  {
    "path": "test/normal/jump/forward/in",
    "content": "%(foo)\nbar\nqux\n"
  },
  {
    "path": "test/normal/jump/forward/out",
    "content": "foo\nbar\nquxend\n"
  },
  {
    "path": "test/normal/jump/forward-count/cmd",
    "content": "gj\n/bar<ret>\n/qux<ret>\n<c-o><c-o><c-o>\n3<tab>\naend<esc>\n"
  },
  {
    "path": "test/normal/jump/forward-count/in",
    "content": "%(foo)\nbar\nqux\n"
  },
  {
    "path": "test/normal/jump/forward-count/out",
    "content": "foo\nbar\nquxend\n"
  },
  {
    "path": "test/normal/jump/forward-invalid-count/cmd",
    "content": "gj\n/bar<ret>\n/qux<ret>\n<c-o><c-o><c-o>\n42<tab>\n"
  },
  {
    "path": "test/normal/jump/forward-invalid-count/error",
    "content": "'exec': no next jump\n"
  },
  {
    "path": "test/normal/jump/forward-invalid-count/in",
    "content": "%(foo)\nbar\nqux\n"
  },
  {
    "path": "test/normal/keep-cmd/cmd",
    "content": "%<a-s>H$grep -E 'foo|bar'<ret>\n"
  },
  {
    "path": "test/normal/keep-cmd/in",
    "content": "foo\nrha\nbar\n"
  },
  {
    "path": "test/normal/keep-cmd/kak_quoted_selections",
    "content": "'foo' 'bar'\n"
  },
  {
    "path": "test/normal/keep-cmd-reg/cmd",
    "content": "%<a-s>H\"a$<ret>\n"
  },
  {
    "path": "test/normal/keep-cmd-reg/in",
    "content": "foo\nrha\nbar\n"
  },
  {
    "path": "test/normal/keep-cmd-reg/kak_quoted_selections",
    "content": "'foo' 'bar'\n"
  },
  {
    "path": "test/normal/keep-cmd-reg/rc",
    "content": "set-register a %{grep -E 'foo|bar'}\n"
  },
  {
    "path": "test/normal/lower-case/cmd",
    "content": "`\n"
  },
  {
    "path": "test/normal/lower-case/in",
    "content": "%(FOO)\n"
  },
  {
    "path": "test/normal/lower-case/kak_quoted_selections",
    "content": "'foo'\n"
  },
  {
    "path": "test/normal/macro/record-macro/cmd",
    "content": "Qimawww<esc>Q%c<c-r>@\n"
  },
  {
    "path": "test/normal/macro/record-macro/out",
    "content": "imawww<esc>\n"
  },
  {
    "path": "test/normal/macro/replay-macro-mapped-word-completion/cmd",
    "content": "Qofo<c-x><c-w><tab><esc>Qq\n"
  },
  {
    "path": "test/normal/macro/replay-macro-mapped-word-completion/in",
    "content": "foobar\n"
  },
  {
    "path": "test/normal/macro/replay-macro-mapped-word-completion/out",
    "content": "foobar\nfoobar\nfoobar\n"
  },
  {
    "path": "test/normal/macro/replay-macro-mapped-word-completion/rc",
    "content": "map global insert <tab> <c-n>\n"
  },
  {
    "path": "test/normal/match-char/cmd",
    "content": "m\n"
  },
  {
    "path": "test/normal/match-char/in",
    "content": "add(a, %(b)) {\n    return a and b\n}\n"
  },
  {
    "path": "test/normal/match-char/kak_quoted_selections",
    "content": "'(a, b)'\n"
  },
  {
    "path": "test/normal/move/down/cmd",
    "content": "j\n"
  },
  {
    "path": "test/normal/move/down/in",
    "content": "%(foo)\nbar\n"
  },
  {
    "path": "test/normal/move/down/kak_quoted_selections",
    "content": "'r'\n"
  },
  {
    "path": "test/normal/move/down-extending/cmd",
    "content": "J\n"
  },
  {
    "path": "test/normal/move/down-extending/in",
    "content": "%(foo)\nbar\n"
  },
  {
    "path": "test/normal/move/down-extending/kak_quoted_selections",
    "content": "'foo\nbar'\n"
  },
  {
    "path": "test/normal/move/left/cmd",
    "content": "h\n"
  },
  {
    "path": "test/normal/move/left/in",
    "content": "foo %(bar)\n"
  },
  {
    "path": "test/normal/move/left/kak_quoted_selections",
    "content": "'a'\n"
  },
  {
    "path": "test/normal/move/left-extending/cmd",
    "content": "H\n"
  },
  {
    "path": "test/normal/move/left-extending/in",
    "content": "foo %(bar)\n"
  },
  {
    "path": "test/normal/move/left-extending/kak_quoted_selections",
    "content": "'ba'\n"
  },
  {
    "path": "test/normal/move/right/cmd",
    "content": "l\n"
  },
  {
    "path": "test/normal/move/right/in",
    "content": "%(foo) bar\n"
  },
  {
    "path": "test/normal/move/right/kak_quoted_selections",
    "content": "' '\n"
  },
  {
    "path": "test/normal/move/right-extending/cmd",
    "content": "L\n"
  },
  {
    "path": "test/normal/move/right-extending/in",
    "content": "%(foo) bar\n"
  },
  {
    "path": "test/normal/move/right-extending/kak_quoted_selections",
    "content": "'foo '\n"
  },
  {
    "path": "test/normal/move/up/cmd",
    "content": "k\n"
  },
  {
    "path": "test/normal/move/up/in",
    "content": "foo\n%(bar)\n"
  },
  {
    "path": "test/normal/move/up/kak_quoted_selections",
    "content": "'o'\n"
  },
  {
    "path": "test/normal/move/up-extending/cmd",
    "content": "K\n"
  },
  {
    "path": "test/normal/move/up-extending/in",
    "content": "foo\n%(bar)\n"
  },
  {
    "path": "test/normal/move/up-extending/kak_quoted_selections",
    "content": "'o\nb'\n"
  },
  {
    "path": "test/normal/next-big-word/cmd",
    "content": "<a-w>\n"
  },
  {
    "path": "test/normal/next-big-word/in",
    "content": "foo %(b)ar-baz qux\n"
  },
  {
    "path": "test/normal/next-big-word/kak_quoted_selections",
    "content": "'bar-baz '\n"
  },
  {
    "path": "test/normal/next-big-word-extending/cmd",
    "content": "<a-W>\n"
  },
  {
    "path": "test/normal/next-big-word-extending/in",
    "content": "%(foo )bar-baz qux\n"
  },
  {
    "path": "test/normal/next-big-word-extending/kak_quoted_selections",
    "content": "'foo bar-baz '\n"
  },
  {
    "path": "test/normal/next-match/cmd",
    "content": "n\n"
  },
  {
    "path": "test/normal/next-match/in",
    "content": "%(foo) bar\n"
  },
  {
    "path": "test/normal/next-match/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/next-match/rc",
    "content": "reg / [a-z]+\n"
  },
  {
    "path": "test/normal/next-match-appending/cmd",
    "content": "N\n"
  },
  {
    "path": "test/normal/next-match-appending/in",
    "content": "%(foo) bar\n"
  },
  {
    "path": "test/normal/next-match-appending/kak_quoted_selections",
    "content": "'foo' 'bar'\n"
  },
  {
    "path": "test/normal/next-match-appending/rc",
    "content": "reg / [a-z]+\n"
  },
  {
    "path": "test/normal/next-word/cmd",
    "content": "w\n"
  },
  {
    "path": "test/normal/next-word/in",
    "content": "foo %(b)ar baz\n"
  },
  {
    "path": "test/normal/next-word/kak_quoted_selections",
    "content": "'bar '\n"
  },
  {
    "path": "test/normal/next-word-extending/cmd",
    "content": "W\n"
  },
  {
    "path": "test/normal/next-word-extending/in",
    "content": "%(foo )bar baz\n"
  },
  {
    "path": "test/normal/next-word-extending/kak_quoted_selections",
    "content": "'foo bar '\n"
  },
  {
    "path": "test/normal/object/around/angle/cmd",
    "content": "<a-a>a\n"
  },
  {
    "path": "test/normal/object/around/angle/in",
    "content": "#include <%(f)oo>\n"
  },
  {
    "path": "test/normal/object/around/angle/kak_quoted_selections",
    "content": "'<foo>'\n"
  },
  {
    "path": "test/normal/object/around/argument/multi-level/cmd",
    "content": "2<a-a>u\n"
  },
  {
    "path": "test/normal/object/around/argument/multi-level/in",
    "content": "somehing (with some parens, (and%( )some, blub and), other stuff)\n\n"
  },
  {
    "path": "test/normal/object/around/argument/multi-level/kak_quoted_selections",
    "content": "' (and some, blub and),'\n"
  },
  {
    "path": "test/normal/object/around/argument/single-level/cmd",
    "content": "<a-a>u\n"
  },
  {
    "path": "test/normal/object/around/argument/single-level/in",
    "content": "something(first, \"someth%(i)ng\", another);\n"
  },
  {
    "path": "test/normal/object/around/argument/single-level/kak_quoted_selections",
    "content": "' \"something\",'\n"
  },
  {
    "path": "test/normal/object/around/big-word/cmd",
    "content": "<a-a><a-w>\n"
  },
  {
    "path": "test/normal/object/around/big-word/in",
    "content": "foo %(b)ar-baz qux\n"
  },
  {
    "path": "test/normal/object/around/big-word/kak_quoted_selections",
    "content": "'bar-baz '\n"
  },
  {
    "path": "test/normal/object/around/braces/cmd",
    "content": "<a-a>B\n"
  },
  {
    "path": "test/normal/object/around/braces/in",
    "content": "{\n  \"%(foo)\": \"bar\"\n}\n"
  },
  {
    "path": "test/normal/object/around/braces/kak_quoted_selections",
    "content": "'{\n  \"foo\": \"bar\"\n}'\n"
  },
  {
    "path": "test/normal/object/around/brackets/cmd",
    "content": "<a-a>r\n"
  },
  {
    "path": "test/normal/object/around/brackets/in",
    "content": "[\n  \"%(foo)\"\n]\n"
  },
  {
    "path": "test/normal/object/around/brackets/kak_quoted_selections",
    "content": "'[\n  \"foo\"\n]'\n"
  },
  {
    "path": "test/normal/object/around/double_quote/cmd",
    "content": "<a-a>Q\n"
  },
  {
    "path": "test/normal/object/around/double_quote/in",
    "content": "foo(\"%(b)ar\")\n"
  },
  {
    "path": "test/normal/object/around/double_quote/kak_quoted_selections",
    "content": "'\"bar\"'\n"
  },
  {
    "path": "test/normal/object/around/grave_quote/cmd",
    "content": "<a-a>g\n"
  },
  {
    "path": "test/normal/object/around/grave_quote/in",
    "content": "echo `%(f)oo`\n"
  },
  {
    "path": "test/normal/object/around/grave_quote/kak_quoted_selections",
    "content": "'`foo`'\n"
  },
  {
    "path": "test/normal/object/around/indent/cmd",
    "content": "<a-a>i\n"
  },
  {
    "path": "test/normal/object/around/indent/in",
    "content": "\n  foo(%(b)ar)\n\n"
  },
  {
    "path": "test/normal/object/around/indent/kak_quoted_selections",
    "content": "'\n  foo(bar)\n\n'\n"
  },
  {
    "path": "test/normal/object/around/paragraph/cmd",
    "content": "<a-a>p\n"
  },
  {
    "path": "test/normal/object/around/paragraph/in",
    "content": "a\nb\n\n%(c)\nd\n\ne\nf\n"
  },
  {
    "path": "test/normal/object/around/paragraph/kak_quoted_selections",
    "content": "'c\nd\n\n'\n"
  },
  {
    "path": "test/normal/object/around/parenthesis/cmd",
    "content": "<a-a>b\n"
  },
  {
    "path": "test/normal/object/around/parenthesis/in",
    "content": "foo(%(b)ar)\n"
  },
  {
    "path": "test/normal/object/around/parenthesis/kak_quoted_selections",
    "content": "'(bar)'\n"
  },
  {
    "path": "test/normal/object/around/sentence/cmd",
    "content": "<a-a>s\n"
  },
  {
    "path": "test/normal/object/around/sentence/in",
    "content": "%(Lorem ipsum) dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.  Ut enim ad minim veniam, quis\nnostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore\neu fugiat nulla pariatur.  Excepteur sint occaecat cupidatat non proident,\nsunt in culpa qui officia deserunt mollit anim id est laborum.\n"
  },
  {
    "path": "test/normal/object/around/sentence/kak_quoted_selections",
    "content": "'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.  '\n"
  },
  {
    "path": "test/normal/object/around/single_quote/cmd",
    "content": "<a-a>q\n"
  },
  {
    "path": "test/normal/object/around/single_quote/in",
    "content": "foo('%(b)ar')\n"
  },
  {
    "path": "test/normal/object/around/single_quote/kak_quoted_selections",
    "content": "''\\''bar'\\'''\n"
  },
  {
    "path": "test/normal/object/around/slash/cmd",
    "content": "<a-a>/\n"
  },
  {
    "path": "test/normal/object/around/slash/in",
    "content": "foo(/%(b)ar/)\n"
  },
  {
    "path": "test/normal/object/around/slash/kak_quoted_selections",
    "content": "'/bar/'\n"
  },
  {
    "path": "test/normal/object/around/word/cmd",
    "content": "<a-a>w\n"
  },
  {
    "path": "test/normal/object/around/word/in",
    "content": "foo %(b)ar baz\n"
  },
  {
    "path": "test/normal/object/around/word/kak_quoted_selections",
    "content": "'bar '\n"
  },
  {
    "path": "test/normal/object/around-parent/angle/cmd",
    "content": "<a-a>a\n"
  },
  {
    "path": "test/normal/object/around-parent/angle/in",
    "content": "<foo %(<foo>) >\n"
  },
  {
    "path": "test/normal/object/around-parent/angle/kak_quoted_selections",
    "content": "'<foo <foo> >'\n"
  },
  {
    "path": "test/normal/object/around-parent/braces/cmd",
    "content": "<a-a>B\n"
  },
  {
    "path": "test/normal/object/around-parent/braces/in",
    "content": "{\n  \"foo\": %({ \"bar\" })\n}\n"
  },
  {
    "path": "test/normal/object/around-parent/braces/kak_quoted_selections",
    "content": "'{\n  \"foo\": { \"bar\" }\n}'\n"
  },
  {
    "path": "test/normal/object/around-parent/brackets/cmd",
    "content": "<a-a>r\n"
  },
  {
    "path": "test/normal/object/around-parent/brackets/in",
    "content": "[\n  %([\"foo\"])\n]\n"
  },
  {
    "path": "test/normal/object/around-parent/brackets/kak_quoted_selections",
    "content": "'[\n  [\"foo\"]\n]'\n"
  },
  {
    "path": "test/normal/object/around-parent/parenthesis/cmd",
    "content": "L<a-a>b\n"
  },
  {
    "path": "test/normal/object/around-parent/parenthesis/in",
    "content": "(foo%((bar)))\n"
  },
  {
    "path": "test/normal/object/around-parent/parenthesis/kak_quoted_selections",
    "content": "'(foo(bar))'\n"
  },
  {
    "path": "test/normal/object/drop-non-whitespace/cmd",
    "content": "<a-a> \n"
  },
  {
    "path": "test/normal/object/drop-non-whitespace/in",
    "content": "wo%(r)d\nfoo %( ) bar\nbaz%( )qux\n"
  },
  {
    "path": "test/normal/object/drop-non-whitespace/kak_quoted_selections",
    "content": "'   ' ' '\n"
  },
  {
    "path": "test/normal/object/end/angle/cmd",
    "content": "]a\n"
  },
  {
    "path": "test/normal/object/end/angle/in",
    "content": "x > y\n"
  },
  {
    "path": "test/normal/object/end/angle/kak_quoted_selections",
    "content": "'x >'\n"
  },
  {
    "path": "test/normal/object/end/argument/cmd",
    "content": "]u\n"
  },
  {
    "path": "test/normal/object/end/argument/in",
    "content": "somehing (with some parens, and%( )some, other stuff)\n"
  },
  {
    "path": "test/normal/object/end/argument/kak_quoted_selections",
    "content": "' some,'\n"
  },
  {
    "path": "test/normal/object/end/big-word/cmd",
    "content": "]<a-w>\n"
  },
  {
    "path": "test/normal/object/end/big-word/in",
    "content": "foo %(b)ar-baz qux\n"
  },
  {
    "path": "test/normal/object/end/big-word/kak_quoted_selections",
    "content": "'bar-baz '\n"
  },
  {
    "path": "test/normal/object/end/braces/cmd",
    "content": "]B\n"
  },
  {
    "path": "test/normal/object/end/braces/in",
    "content": "kak unit/test/%({)cmd,in,out}\n"
  },
  {
    "path": "test/normal/object/end/braces/kak_quoted_selections",
    "content": "'{cmd,in,out}'\n"
  },
  {
    "path": "test/normal/object/end/brackets/cmd",
    "content": "]r\n"
  },
  {
    "path": "test/normal/object/end/brackets/in",
    "content": "[1,%(2),3]\n"
  },
  {
    "path": "test/normal/object/end/brackets/kak_quoted_selections",
    "content": "'2,3]'\n"
  },
  {
    "path": "test/normal/object/end/double_quote/cmd",
    "content": "]Q\n"
  },
  {
    "path": "test/normal/object/end/double_quote/in",
    "content": "foo(\"%(b)ar\")\n"
  },
  {
    "path": "test/normal/object/end/double_quote/kak_quoted_selections",
    "content": "'bar\"'\n"
  },
  {
    "path": "test/normal/object/end/grave_quote/cmd",
    "content": "]g\n"
  },
  {
    "path": "test/normal/object/end/grave_quote/in",
    "content": "`%(f)oo`\n"
  },
  {
    "path": "test/normal/object/end/grave_quote/kak_quoted_selections",
    "content": "'foo`'\n"
  },
  {
    "path": "test/normal/object/end/indent/cmd",
    "content": "]i\n"
  },
  {
    "path": "test/normal/object/end/indent/in",
    "content": "\n  foo(%(b)ar)\n\n"
  },
  {
    "path": "test/normal/object/end/indent/kak_quoted_selections",
    "content": "'bar)\n\n'\n"
  },
  {
    "path": "test/normal/object/end/paragraph/count/cmd",
    "content": "2]p\n"
  },
  {
    "path": "test/normal/object/end/paragraph/count/in",
    "content": "%(a)\nb\n\nc\nd\n\ne\n"
  },
  {
    "path": "test/normal/object/end/paragraph/count/kak_quoted_selections",
    "content": "'a\nb\n\nc\nd\n\n'\n"
  },
  {
    "path": "test/normal/object/end/paragraph/single/cmd",
    "content": "]p\n"
  },
  {
    "path": "test/normal/object/end/paragraph/single/in",
    "content": "a\nb\n\n%(c)\nd\n\ne\nf\n"
  },
  {
    "path": "test/normal/object/end/paragraph/single/kak_quoted_selections",
    "content": "'c\nd\n\n'\n"
  },
  {
    "path": "test/normal/object/end/paragraph/to-buffer-end/cmd",
    "content": "9]p\n"
  },
  {
    "path": "test/normal/object/end/paragraph/to-buffer-end/in",
    "content": "%(a)\nb\n\nc\n"
  },
  {
    "path": "test/normal/object/end/paragraph/to-buffer-end/kak_quoted_selections",
    "content": "'a\nb\n\nc\n'\n"
  },
  {
    "path": "test/normal/object/end/parenthesis/cmd",
    "content": "]b\n"
  },
  {
    "path": "test/normal/object/end/parenthesis/in",
    "content": "foo(%(b)ar)\n"
  },
  {
    "path": "test/normal/object/end/parenthesis/kak_quoted_selections",
    "content": "'bar)'\n"
  },
  {
    "path": "test/normal/object/end/sentence/count/cmd",
    "content": "<a-;>2]s\n"
  },
  {
    "path": "test/normal/object/end/sentence/count/in",
    "content": "%(a b) c. d e. f g.\n"
  },
  {
    "path": "test/normal/object/end/sentence/count/kak_quoted_selections",
    "content": "'a b c. d e. '\n"
  },
  {
    "path": "test/normal/object/end/sentence/single/cmd",
    "content": "]s\n"
  },
  {
    "path": "test/normal/object/end/sentence/single/in",
    "content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.  Ut enim ad minim veniam, quis\nnostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore\neu fugiat nulla pariatur.  Excepteur sint occaecat cupidatat non proident,\nsunt in culpa qui officia deserunt mollit anim id est laborum.\n"
  },
  {
    "path": "test/normal/object/end/sentence/single/kak_quoted_selections",
    "content": "'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.  '\n"
  },
  {
    "path": "test/normal/object/end/sentence/to-buffer-end/cmd",
    "content": "9]s\n"
  },
  {
    "path": "test/normal/object/end/sentence/to-buffer-end/in",
    "content": "a b.\n\nc d.\n"
  },
  {
    "path": "test/normal/object/end/sentence/to-buffer-end/kak_quoted_selections",
    "content": "'a b.'\n"
  },
  {
    "path": "test/normal/object/end/single_quote/cmd",
    "content": "]q\n"
  },
  {
    "path": "test/normal/object/end/single_quote/in",
    "content": "foo('%(b)ar')\n"
  },
  {
    "path": "test/normal/object/end/single_quote/kak_quoted_selections",
    "content": "'bar'\\'''\n"
  },
  {
    "path": "test/normal/object/end/word/cmd",
    "content": "]w\n"
  },
  {
    "path": "test/normal/object/end/word/in",
    "content": "foo %(b)ar baz\n"
  },
  {
    "path": "test/normal/object/end/word/kak_quoted_selections",
    "content": "'bar '\n"
  },
  {
    "path": "test/normal/object/end-extending/angle/cmd",
    "content": "}a\n"
  },
  {
    "path": "test/normal/object/end-extending/angle/in",
    "content": "#include <%(foo)>\n"
  },
  {
    "path": "test/normal/object/end-extending/angle/kak_quoted_selections",
    "content": "'foo>'\n"
  },
  {
    "path": "test/normal/object/end-extending/argument/cmd",
    "content": "}u\n"
  },
  {
    "path": "test/normal/object/end-extending/argument/in",
    "content": "something (with a paren, and %(some) stuff, and more)\n"
  },
  {
    "path": "test/normal/object/end-extending/argument/kak_quoted_selections",
    "content": "'some stuff,'\n"
  },
  {
    "path": "test/normal/object/end-extending/big-word/cmd",
    "content": "}<a-w>\n"
  },
  {
    "path": "test/normal/object/end-extending/big-word/in",
    "content": "foo %(b)ar-baz qux\n"
  },
  {
    "path": "test/normal/object/end-extending/big-word/kak_quoted_selections",
    "content": "'bar-baz '\n"
  },
  {
    "path": "test/normal/object/end-extending/braces/cmd",
    "content": "}B\n"
  },
  {
    "path": "test/normal/object/end-extending/braces/in",
    "content": "{\n  \"%(foo)\": \"bar\"\n}\n"
  },
  {
    "path": "test/normal/object/end-extending/braces/kak_quoted_selections",
    "content": "'foo\": \"bar\"\n}'\n"
  },
  {
    "path": "test/normal/object/end-extending/brackets/cmd",
    "content": "}r\n"
  },
  {
    "path": "test/normal/object/end-extending/brackets/in",
    "content": "[\n  \"%(foo)\"\n]\n"
  },
  {
    "path": "test/normal/object/end-extending/brackets/kak_quoted_selections",
    "content": "'foo\"\n]'\n"
  },
  {
    "path": "test/normal/object/end-extending/double_quote/cmd",
    "content": "}Q\n"
  },
  {
    "path": "test/normal/object/end-extending/double_quote/in",
    "content": "foo(\"%(b)ar\")\n"
  },
  {
    "path": "test/normal/object/end-extending/double_quote/kak_quoted_selections",
    "content": "'bar\"'\n"
  },
  {
    "path": "test/normal/object/end-extending/grave_quote/cmd",
    "content": "}g\n"
  },
  {
    "path": "test/normal/object/end-extending/grave_quote/in",
    "content": "echo `%(f)oo`\n"
  },
  {
    "path": "test/normal/object/end-extending/grave_quote/kak_quoted_selections",
    "content": "'foo`'\n"
  },
  {
    "path": "test/normal/object/end-extending/indent/cmd",
    "content": "}i\n"
  },
  {
    "path": "test/normal/object/end-extending/indent/in",
    "content": "\n  foo(%(b)ar)\n\n"
  },
  {
    "path": "test/normal/object/end-extending/indent/kak_quoted_selections",
    "content": "'bar)\n\n'\n"
  },
  {
    "path": "test/normal/object/end-extending/paragraph/count/cmd",
    "content": "2}p\n"
  },
  {
    "path": "test/normal/object/end-extending/paragraph/count/in",
    "content": "%(a\nb)\n\nc\nd\n\ne\n"
  },
  {
    "path": "test/normal/object/end-extending/paragraph/count/kak_quoted_selections",
    "content": "'a\nb\n\nc\nd\n\n'\n"
  },
  {
    "path": "test/normal/object/end-extending/paragraph/single/cmd",
    "content": "}p\n"
  },
  {
    "path": "test/normal/object/end-extending/paragraph/single/in",
    "content": "a\nb\n\n%(c)\nd\n\ne\nf\n"
  },
  {
    "path": "test/normal/object/end-extending/paragraph/single/kak_quoted_selections",
    "content": "'c\nd\n\n'\n"
  },
  {
    "path": "test/normal/object/end-extending/parenthesis/cmd",
    "content": "}b\n"
  },
  {
    "path": "test/normal/object/end-extending/parenthesis/in",
    "content": "foo(%(b)ar)\n"
  },
  {
    "path": "test/normal/object/end-extending/parenthesis/kak_quoted_selections",
    "content": "'bar)'\n"
  },
  {
    "path": "test/normal/object/end-extending/sentence/count/cmd",
    "content": "2}s\n"
  },
  {
    "path": "test/normal/object/end-extending/sentence/count/in",
    "content": "%(a b) c. d e. f.\n"
  },
  {
    "path": "test/normal/object/end-extending/sentence/count/kak_quoted_selections",
    "content": "'a b c. d e. '\n"
  },
  {
    "path": "test/normal/object/end-extending/sentence/single/cmd",
    "content": "}s\n"
  },
  {
    "path": "test/normal/object/end-extending/sentence/single/in",
    "content": "%(Lorem ipsum) dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.  Ut enim ad minim veniam, quis\nnostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore\neu fugiat nulla pariatur.  Excepteur sint occaecat cupidatat non proident,\nsunt in culpa qui officia deserunt mollit anim id est laborum.\n"
  },
  {
    "path": "test/normal/object/end-extending/sentence/single/kak_quoted_selections",
    "content": "'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.  '\n"
  },
  {
    "path": "test/normal/object/end-extending/single_quote/cmd",
    "content": "}q\n"
  },
  {
    "path": "test/normal/object/end-extending/single_quote/in",
    "content": "foo('%(b)ar')\n"
  },
  {
    "path": "test/normal/object/end-extending/single_quote/kak_quoted_selections",
    "content": "'bar'\\'''\n"
  },
  {
    "path": "test/normal/object/end-extending/word/cmd",
    "content": "}w\n"
  },
  {
    "path": "test/normal/object/end-extending/word/in",
    "content": "foo %(b)ar baz\n"
  },
  {
    "path": "test/normal/object/end-extending/word/kak_quoted_selections",
    "content": "'bar '\n"
  },
  {
    "path": "test/normal/object/end-extending-parent/braces/cmd",
    "content": "}B}B\n"
  },
  {
    "path": "test/normal/object/end-extending-parent/braces/in",
    "content": "{\n  \"foo\": { %(bar) }\n}\n"
  },
  {
    "path": "test/normal/object/end-extending-parent/braces/kak_quoted_selections",
    "content": "'bar }\n}'\n"
  },
  {
    "path": "test/normal/object/inner/angle/cmd",
    "content": "<a-i>a\n"
  },
  {
    "path": "test/normal/object/inner/angle/in",
    "content": "#include <%(f)oo>\n"
  },
  {
    "path": "test/normal/object/inner/angle/kak_quoted_selections",
    "content": "'foo'\n"
  },
  {
    "path": "test/normal/object/inner/argument/multi-level/cmd",
    "content": "2<a-i>u\n"
  },
  {
    "path": "test/normal/object/inner/argument/multi-level/in",
    "content": "somehing (with some parens, (and%( )some, blub and), other stuff)\n"
  },
  {
    "path": "test/normal/object/inner/argument/multi-level/kak_quoted_selections",
    "content": "'(and some, blub and)'\n"
  },
  {
    "path": "test/normal/object/inner/argument/single-level/cmd",
    "content": "<a-i>u\n"
  },
  {
    "path": "test/normal/object/inner/argument/single-level/in",
    "content": "somehing (with some parens, and%( )some, other stuff)\n"
  },
  {
    "path": "test/normal/object/inner/argument/single-level/kak_quoted_selections",
    "content": "'and some'\n"
  },
  {
    "path": "test/normal/object/inner/big-word/cmd",
    "content": "<a-i><a-w>\n"
  },
  {
    "path": "test/normal/object/inner/big-word/in",
    "content": "foo %(b)ar-baz qux\n"
  },
  {
    "path": "test/normal/object/inner/big-word/kak_quoted_selections",
    "content": "'bar-baz'\n"
  },
  {
    "path": "test/normal/object/inner/braces/cmd",
    "content": "<a-a>B\n"
  },
  {
    "path": "test/normal/object/inner/braces/in",
    "content": "{\n  \"%(foo)\"= \"bar\"\n}\n"
  },
  {
    "path": "test/normal/object/inner/braces/kak_quoted_selections",
    "content": "'{\n  \"foo\"= \"bar\"\n}'\n"
  },
  {
    "path": "test/normal/object/inner/brackets/cmd",
    "content": "<a-a>r\n"
  },
  {
    "path": "test/normal/object/inner/brackets/in",
    "content": "[\n  \"%(foo)\"\n]\n"
  },
  {
    "path": "test/normal/object/inner/brackets/kak_quoted_selections",
    "content": "'[\n  \"foo\"\n]'\n"
  },
  {
    "path": "test/normal/object/inner/double_quote/cmd",
    "content": "<a-i>Q\n"
  },
  {
    "path": "test/normal/object/inner/double_quote/in",
    "content": "foo(\"%(b)ar\")\n"
  },
  {
    "path": "test/normal/object/inner/double_quote/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/inner/empty-braces/cmd",
    "content": "<a-i>{\n"
  },
  {
    "path": "test/normal/object/inner/empty-braces/error",
    "content": "'exec': no selections remaining\n"
  },
  {
    "path": "test/normal/object/inner/empty-braces/in",
    "content": "%({)} {%({)}} {%(}) {{%(})}\n"
  },
  {
    "path": "test/normal/object/inner/empty-double_quote/cmd",
    "content": "<a-i>{\n"
  },
  {
    "path": "test/normal/object/inner/empty-double_quote/error",
    "content": "'exec': no selections remaining\n"
  },
  {
    "path": "test/normal/object/inner/empty-double_quote/in",
    "content": "\"\"\n"
  },
  {
    "path": "test/normal/object/inner/grave_quote/cmd",
    "content": "<a-i>g\n"
  },
  {
    "path": "test/normal/object/inner/grave_quote/in",
    "content": "echo `%(f)oo`\n"
  },
  {
    "path": "test/normal/object/inner/grave_quote/kak_quoted_selections",
    "content": "'foo'\n"
  },
  {
    "path": "test/normal/object/inner/indent/cmd",
    "content": "<a-i>i\n"
  },
  {
    "path": "test/normal/object/inner/indent/in",
    "content": "\n  foo(%(b)ar)\n\n"
  },
  {
    "path": "test/normal/object/inner/indent/kak_quoted_selections",
    "content": "'  foo(bar)\n'\n"
  },
  {
    "path": "test/normal/object/inner/paragraph/cmd",
    "content": "<a-i>p\n"
  },
  {
    "path": "test/normal/object/inner/paragraph/in",
    "content": "a\nb\n\n%(c)\nd\n\ne\nf\n"
  },
  {
    "path": "test/normal/object/inner/paragraph/kak_quoted_selections",
    "content": "'c\nd\n'\n"
  },
  {
    "path": "test/normal/object/inner/parenthesis/cmd",
    "content": "<a-i>b\n"
  },
  {
    "path": "test/normal/object/inner/parenthesis/in",
    "content": "foo(%(b)ar)\n"
  },
  {
    "path": "test/normal/object/inner/parenthesis/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/inner/sentence/cmd",
    "content": "<a-i>s\n"
  },
  {
    "path": "test/normal/object/inner/sentence/in",
    "content": "%(Lorem ipsum) dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.  Ut enim ad minim veniam, quis\nnostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore\neu fugiat nulla pariatur.  Excepteur sint occaecat cupidatat non proident,\nsunt in culpa qui officia deserunt mollit anim id est laborum.\n"
  },
  {
    "path": "test/normal/object/inner/sentence/kak_quoted_selections",
    "content": "'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua.'\n"
  },
  {
    "path": "test/normal/object/inner/single_quote/cmd",
    "content": "<a-i>q\n"
  },
  {
    "path": "test/normal/object/inner/single_quote/in",
    "content": "foo('%(b)ar')\n"
  },
  {
    "path": "test/normal/object/inner/single_quote/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/inner/slash/cmd",
    "content": "<a-i>/\n"
  },
  {
    "path": "test/normal/object/inner/slash/in",
    "content": "foo(/%(b)ar/)\n"
  },
  {
    "path": "test/normal/object/inner/slash/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/inner/word/cmd",
    "content": "<a-i>w\n"
  },
  {
    "path": "test/normal/object/inner/word/in",
    "content": "foo %(b)ar baz\n"
  },
  {
    "path": "test/normal/object/inner/word/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/nested/around/arguments/cmd",
    "content": "<a-l><a-A>u\n"
  },
  {
    "path": "test/normal/object/nested/around/arguments/in",
    "content": "foo(), bar(baz(), qux(foo{})), 10, {}\n"
  },
  {
    "path": "test/normal/object/nested/around/arguments/kak_quoted_selections",
    "content": "'foo(),' ' bar(baz(), qux(foo{})),' ' 10,' ' {}'\n"
  },
  {
    "path": "test/normal/object/nested/around/big-word/cmd",
    "content": "%<a-A><a-w>\n"
  },
  {
    "path": "test/normal/object/nested/around/big-word/in",
    "content": "foo bar-baz foo_bar\nfoo.bar bàz\n"
  },
  {
    "path": "test/normal/object/nested/around/big-word/kak_quoted_selections",
    "content": "'foo ' 'bar-baz ' 'foo_bar' 'foo.bar ' 'bàz'\n"
  },
  {
    "path": "test/normal/object/nested/around/curly-braces/cmd",
    "content": "%<a-A>{\n"
  },
  {
    "path": "test/normal/object/nested/around/curly-braces/in",
    "content": "{ foo }{ bar } {} { foo { bar } baz } } { { qux } }\n"
  },
  {
    "path": "test/normal/object/nested/around/curly-braces/kak_quoted_selections",
    "content": "'{ foo }' '{ bar }' '{}' '{ foo { bar } baz }' '{ qux }'\n"
  },
  {
    "path": "test/normal/object/nested/around/double-quotes/cmd",
    "content": "%<a-A>\"\n"
  },
  {
    "path": "test/normal/object/nested/around/double-quotes/in",
    "content": "\"foo\"\"bar\" \"\" \"foo\" \"qux\n"
  },
  {
    "path": "test/normal/object/nested/around/double-quotes/kak_quoted_selections",
    "content": "'\"foo\"' '\"bar\"' '\"\"' '\"foo\"' '\"qux\n'\n"
  },
  {
    "path": "test/normal/object/nested/around/number/cmd",
    "content": "%<a-A>n\n"
  },
  {
    "path": "test/normal/object/nested/around/number/in",
    "content": "1 23 456- -789 . -. 1.23 -5.67 1. .89\n"
  },
  {
    "path": "test/normal/object/nested/around/number/kak_quoted_selections",
    "content": "'1' '23' '456' '-789' '1.23' '-5.67' '1.' '.89'\n"
  },
  {
    "path": "test/normal/object/nested/around/paragraph/cmd",
    "content": "%<a-A>p\n"
  },
  {
    "path": "test/normal/object/nested/around/paragraph/in",
    "content": "foo bar baz\nfoo bar\n\nbaz qux foo\n\n\nfoo\n"
  },
  {
    "path": "test/normal/object/nested/around/paragraph/kak_quoted_selections",
    "content": "'foo bar baz\nfoo bar\n\n' 'baz qux foo\n\n\n' 'foo\n'\n"
  },
  {
    "path": "test/normal/object/nested/around/sentence/cmd",
    "content": "%<a-A>s\n"
  },
  {
    "path": "test/normal/object/nested/around/sentence/in",
    "content": "foo bar baz; foo qux.\nfoo baz\nbar ? Qux !\n"
  },
  {
    "path": "test/normal/object/nested/around/sentence/kak_quoted_selections",
    "content": "'foo bar baz; ' 'foo qux.' 'foo baz\nbar ? ' 'Qux !'\n"
  },
  {
    "path": "test/normal/object/nested/around/whitespaces/cmd",
    "content": "%<a-A><space>\n"
  },
  {
    "path": "test/normal/object/nested/around/whitespaces/in",
    "content": "foo bar  baz\nqux\n"
  },
  {
    "path": "test/normal/object/nested/around/whitespaces/kak_quoted_selections",
    "content": "' ' '  ' '\n' '\n'\n"
  },
  {
    "path": "test/normal/object/nested/around/word/cmd",
    "content": "%<a-A>w\n"
  },
  {
    "path": "test/normal/object/nested/around/word/in",
    "content": "foo bar-baz foo_bar\nfoo.bar bàz\n"
  },
  {
    "path": "test/normal/object/nested/around/word/kak_quoted_selections",
    "content": "'foo ' 'bar' 'baz ' 'foo_bar' 'foo' 'bar ' 'bàz'\n"
  },
  {
    "path": "test/normal/object/nested/inner/arguments/cmd",
    "content": "<a-l><a-I>u\n"
  },
  {
    "path": "test/normal/object/nested/inner/arguments/in",
    "content": "foo(), bar(baz(), qux(foo{})), 10, {}\n"
  },
  {
    "path": "test/normal/object/nested/inner/arguments/kak_quoted_selections",
    "content": "'foo()' 'bar(baz(), qux(foo{}))' '10' '{}'\n"
  },
  {
    "path": "test/normal/object/nested/inner/big-word/cmd",
    "content": "%<a-I><a-w>\n"
  },
  {
    "path": "test/normal/object/nested/inner/big-word/in",
    "content": "foo bar-baz foo_bar\nfoo.bar bàz\n"
  },
  {
    "path": "test/normal/object/nested/inner/big-word/kak_quoted_selections",
    "content": "'foo' 'bar-baz' 'foo_bar' 'foo.bar' 'bàz'\n"
  },
  {
    "path": "test/normal/object/nested/inner/curly-braces/cmd",
    "content": "%<a-I>{\n"
  },
  {
    "path": "test/normal/object/nested/inner/curly-braces/in",
    "content": "{ foo }{ bar } {} { foo { bar } baz } } { { qux } }\n"
  },
  {
    "path": "test/normal/object/nested/inner/curly-braces/kak_quoted_selections",
    "content": "' foo ' ' bar ' ' foo { bar } baz ' ' qux '\n"
  },
  {
    "path": "test/normal/object/nested/inner/double-quotes/cmd",
    "content": "%<a-I>\"\n"
  },
  {
    "path": "test/normal/object/nested/inner/double-quotes/in",
    "content": "\"foo\"\"bar\" \"\" \"foo\" \"qux\n"
  },
  {
    "path": "test/normal/object/nested/inner/double-quotes/kak_quoted_selections",
    "content": "'foo' 'bar' 'foo' 'qux\n'\n"
  },
  {
    "path": "test/normal/object/nested/inner/number/cmd",
    "content": "%<a-I>n\n"
  },
  {
    "path": "test/normal/object/nested/inner/number/in",
    "content": "1 23 456- -789 . -. 1.23 -5.67 1. .89\n"
  },
  {
    "path": "test/normal/object/nested/inner/number/kak_quoted_selections",
    "content": "'1' '23' '456' '-789' '1' '23' '-5' '67' '1' '89'\n"
  },
  {
    "path": "test/normal/object/nested/inner/paragraph/cmd",
    "content": "%<a-I>p\n"
  },
  {
    "path": "test/normal/object/nested/inner/paragraph/in",
    "content": "foo bar baz\nfoo bar\n\nbaz qux foo\n\n\nfoo\n"
  },
  {
    "path": "test/normal/object/nested/inner/paragraph/kak_quoted_selections",
    "content": "'foo bar baz\nfoo bar\n' 'baz qux foo\n' 'foo\n'\n"
  },
  {
    "path": "test/normal/object/nested/inner/sentence/cmd",
    "content": "%<a-I>s\n"
  },
  {
    "path": "test/normal/object/nested/inner/sentence/in",
    "content": "foo bar baz; foo qux.\nfoo baz\nbar ? Qux !\n"
  },
  {
    "path": "test/normal/object/nested/inner/sentence/kak_quoted_selections",
    "content": "'foo bar baz;' 'foo qux.' 'foo baz\nbar ?' 'Qux !'\n"
  },
  {
    "path": "test/normal/object/nested/inner/whitespaces/cmd",
    "content": "%<a-I><space>\n"
  },
  {
    "path": "test/normal/object/nested/inner/whitespaces/in",
    "content": "foo bar  baz\nqux\n"
  },
  {
    "path": "test/normal/object/nested/inner/whitespaces/kak_quoted_selections",
    "content": "' ' '  '\n"
  },
  {
    "path": "test/normal/object/nested/inner/word/cmd",
    "content": "%<a-I>w\n"
  },
  {
    "path": "test/normal/object/nested/inner/word/in",
    "content": "foo bar-baz foo_bar\nfoo.bar bàz\n"
  },
  {
    "path": "test/normal/object/nested/inner/word/kak_quoted_selections",
    "content": "'foo' 'bar' 'baz' 'foo_bar' 'foo' 'bar' 'bàz'\n"
  },
  {
    "path": "test/normal/object/on-end/around/angle/cmd",
    "content": "<a-a>a\n"
  },
  {
    "path": "test/normal/object/on-end/around/angle/in",
    "content": "#include <foo%(>)\n"
  },
  {
    "path": "test/normal/object/on-end/around/angle/kak_quoted_selections",
    "content": "'<foo>'\n"
  },
  {
    "path": "test/normal/object/on-end/around/braces/cmd",
    "content": "<a-a>B\n"
  },
  {
    "path": "test/normal/object/on-end/around/braces/in",
    "content": "{\n  \"foo\": \"bar\"\n%(})\n"
  },
  {
    "path": "test/normal/object/on-end/around/braces/kak_quoted_selections",
    "content": "'{\n  \"foo\": \"bar\"\n}'\n"
  },
  {
    "path": "test/normal/object/on-end/around/brackets/cmd",
    "content": "<a-a>r\n"
  },
  {
    "path": "test/normal/object/on-end/around/brackets/in",
    "content": "[\n  \"foo\"\n%(])\n"
  },
  {
    "path": "test/normal/object/on-end/around/brackets/kak_quoted_selections",
    "content": "'[\n  \"foo\"\n]'\n"
  },
  {
    "path": "test/normal/object/on-end/around/parenthesis/cmd",
    "content": "l<a-a>b\n"
  },
  {
    "path": "test/normal/object/on-end/around/parenthesis/in",
    "content": "foo(ba%(r))\n"
  },
  {
    "path": "test/normal/object/on-end/around/parenthesis/kak_quoted_selections",
    "content": "'(bar)'\n"
  },
  {
    "path": "test/normal/object/on-end/inner/angle/cmd",
    "content": "<a-i>a\n"
  },
  {
    "path": "test/normal/object/on-end/inner/angle/in",
    "content": "#include <foo%(>)\n"
  },
  {
    "path": "test/normal/object/on-end/inner/angle/kak_quoted_selections",
    "content": "'foo'\n"
  },
  {
    "path": "test/normal/object/on-end/inner/braces/cmd",
    "content": "<a-a>B\n"
  },
  {
    "path": "test/normal/object/on-end/inner/braces/in",
    "content": "{\n  \"foo\"= \"bar\"\n%(})\n"
  },
  {
    "path": "test/normal/object/on-end/inner/braces/kak_quoted_selections",
    "content": "'{\n  \"foo\"= \"bar\"\n}'\n"
  },
  {
    "path": "test/normal/object/on-end/inner/brackets/cmd",
    "content": "<a-a>r\n"
  },
  {
    "path": "test/normal/object/on-end/inner/brackets/in",
    "content": "[\n  \"foo\"\n%(])\n"
  },
  {
    "path": "test/normal/object/on-end/inner/brackets/kak_quoted_selections",
    "content": "'[\n  \"foo\"\n]'\n"
  },
  {
    "path": "test/normal/object/on-end/inner/parenthesis/cmd",
    "content": "l<a-i>b\n"
  },
  {
    "path": "test/normal/object/on-end/inner/parenthesis/in",
    "content": "foo(ba%(r))\n"
  },
  {
    "path": "test/normal/object/on-end/inner/parenthesis/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/angle/cmd",
    "content": "]a\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/angle/in",
    "content": "<foo <bar%(>) >\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/angle/kak_quoted_selections",
    "content": "'> >'\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/braces/cmd",
    "content": "]B\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/braces/in",
    "content": "{\n  { \"foo\": \"bar\" %(})\n}\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/braces/kak_quoted_selections",
    "content": "'}\n}'\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/brackets/cmd",
    "content": "]r\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/brackets/in",
    "content": "[\n  [\"foo\"%(])\n]\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/brackets/kak_quoted_selections",
    "content": "']\n]'\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/parenthesis/cmd",
    "content": "l]b\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/parenthesis/in",
    "content": "(foo(ba%(r)))\n"
  },
  {
    "path": "test/normal/object/on-end/to-end/parenthesis/kak_quoted_selections",
    "content": "'))'\n"
  },
  {
    "path": "test/normal/object/on-start/around/angle/cmd",
    "content": "<a-a>a\n"
  },
  {
    "path": "test/normal/object/on-start/around/angle/in",
    "content": "#include %(<)foo>\n"
  },
  {
    "path": "test/normal/object/on-start/around/angle/kak_quoted_selections",
    "content": "'<foo>'\n"
  },
  {
    "path": "test/normal/object/on-start/around/braces/cmd",
    "content": "<a-a>B\n"
  },
  {
    "path": "test/normal/object/on-start/around/braces/in",
    "content": "%({)\n  \"foo\": \"bar\"\n}\n"
  },
  {
    "path": "test/normal/object/on-start/around/braces/kak_quoted_selections",
    "content": "'{\n  \"foo\": \"bar\"\n}'\n"
  },
  {
    "path": "test/normal/object/on-start/around/brackets/cmd",
    "content": "<a-a>r\n"
  },
  {
    "path": "test/normal/object/on-start/around/brackets/in",
    "content": "%([)\n  \"foo\"\n]\n"
  },
  {
    "path": "test/normal/object/on-start/around/brackets/kak_quoted_selections",
    "content": "'[\n  \"foo\"\n]'\n"
  },
  {
    "path": "test/normal/object/on-start/around/double_quote/cmd",
    "content": "<a-a>Q\n"
  },
  {
    "path": "test/normal/object/on-start/around/double_quote/in",
    "content": "foo(%(\")bar\")\n"
  },
  {
    "path": "test/normal/object/on-start/around/double_quote/kak_quoted_selections",
    "content": "'\"bar\"'\n"
  },
  {
    "path": "test/normal/object/on-start/around/grave_quote/cmd",
    "content": "<a-a>g\n"
  },
  {
    "path": "test/normal/object/on-start/around/grave_quote/in",
    "content": "echo %(`)foo`\n"
  },
  {
    "path": "test/normal/object/on-start/around/grave_quote/kak_quoted_selections",
    "content": "'`foo`'\n"
  },
  {
    "path": "test/normal/object/on-start/around/parenthesis/cmd",
    "content": "<a-a>b\n"
  },
  {
    "path": "test/normal/object/on-start/around/parenthesis/in",
    "content": "foo%(()bar)\n"
  },
  {
    "path": "test/normal/object/on-start/around/parenthesis/kak_quoted_selections",
    "content": "'(bar)'\n"
  },
  {
    "path": "test/normal/object/on-start/around/single_quote/cmd",
    "content": "<a-a>q\n"
  },
  {
    "path": "test/normal/object/on-start/around/single_quote/in",
    "content": "foo(%(')bar')\n"
  },
  {
    "path": "test/normal/object/on-start/around/single_quote/kak_quoted_selections",
    "content": "''\\''bar'\\'''\n"
  },
  {
    "path": "test/normal/object/on-start/around/slash/cmd",
    "content": "<a-a>/\n"
  },
  {
    "path": "test/normal/object/on-start/around/slash/in",
    "content": "foo(%(/)bar/)\n"
  },
  {
    "path": "test/normal/object/on-start/around/slash/kak_quoted_selections",
    "content": "'/bar/'\n"
  },
  {
    "path": "test/normal/object/on-start/inner/angle/cmd",
    "content": "<a-i>a\n"
  },
  {
    "path": "test/normal/object/on-start/inner/angle/in",
    "content": "#include %(<)foo>\n"
  },
  {
    "path": "test/normal/object/on-start/inner/angle/kak_quoted_selections",
    "content": "'foo'\n"
  },
  {
    "path": "test/normal/object/on-start/inner/braces/cmd",
    "content": "<a-a>B\n"
  },
  {
    "path": "test/normal/object/on-start/inner/braces/in",
    "content": "%({)\n  \"foo\"= \"bar\"\n}\n"
  },
  {
    "path": "test/normal/object/on-start/inner/braces/kak_quoted_selections",
    "content": "'{\n  \"foo\"= \"bar\"\n}'\n"
  },
  {
    "path": "test/normal/object/on-start/inner/brackets/cmd",
    "content": "<a-a>r\n"
  },
  {
    "path": "test/normal/object/on-start/inner/brackets/in",
    "content": "%([)\n  \"foo\"\n]\n"
  },
  {
    "path": "test/normal/object/on-start/inner/brackets/kak_quoted_selections",
    "content": "'[\n  \"foo\"\n]'\n"
  },
  {
    "path": "test/normal/object/on-start/inner/double_quote/cmd",
    "content": "<a-i>Q\n"
  },
  {
    "path": "test/normal/object/on-start/inner/double_quote/in",
    "content": "foo(%(\")bar\")\n"
  },
  {
    "path": "test/normal/object/on-start/inner/double_quote/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/on-start/inner/grave_quote/cmd",
    "content": "<a-i>g\n"
  },
  {
    "path": "test/normal/object/on-start/inner/grave_quote/in",
    "content": "echo %(`)foo`\n"
  },
  {
    "path": "test/normal/object/on-start/inner/grave_quote/kak_quoted_selections",
    "content": "'foo'\n"
  },
  {
    "path": "test/normal/object/on-start/inner/parenthesis/cmd",
    "content": "<a-i>b\n"
  },
  {
    "path": "test/normal/object/on-start/inner/parenthesis/in",
    "content": "foo%(()bar)\n"
  },
  {
    "path": "test/normal/object/on-start/inner/parenthesis/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/on-start/inner/single_quote/cmd",
    "content": "<a-i>q\n"
  },
  {
    "path": "test/normal/object/on-start/inner/single_quote/in",
    "content": "foo(%(')bar')\n"
  },
  {
    "path": "test/normal/object/on-start/inner/single_quote/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/angle/cmd",
    "content": "[a\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/angle/in",
    "content": "<foo %(<)bar> >\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/angle/kak_quoted_selections",
    "content": "'<foo <'\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/braces/cmd",
    "content": "[B\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/braces/in",
    "content": "{\n  %({) \"foo\": \"bar\" }\n}\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/braces/kak_quoted_selections",
    "content": "'{\n  {'\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/brackets/cmd",
    "content": "[r\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/brackets/in",
    "content": "[\n  %([)\"foo\"]\n]\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/brackets/kak_quoted_selections",
    "content": "'[\n  ['\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/parenthesis/cmd",
    "content": "[b\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/parenthesis/in",
    "content": "(foo%(()bar))\n"
  },
  {
    "path": "test/normal/object/on-start/to-start/parenthesis/kak_quoted_selections",
    "content": "'(foo('\n"
  },
  {
    "path": "test/normal/object/start/angle/cmd",
    "content": "[a\n"
  },
  {
    "path": "test/normal/object/start/angle/in",
    "content": "#include <%(f)oo>\n"
  },
  {
    "path": "test/normal/object/start/angle/kak_quoted_selections",
    "content": "'<f'\n"
  },
  {
    "path": "test/normal/object/start/argument/cmd",
    "content": "[u\n"
  },
  {
    "path": "test/normal/object/start/argument/in",
    "content": "something (with a p%(a)ren, and some stuff)\n"
  },
  {
    "path": "test/normal/object/start/argument/kak_quoted_selections",
    "content": "'with a pa'\n"
  },
  {
    "path": "test/normal/object/start/big-word/cmd",
    "content": "[<a-w>\n"
  },
  {
    "path": "test/normal/object/start/big-word/in",
    "content": "foo bar-ba%(z) qux\n"
  },
  {
    "path": "test/normal/object/start/big-word/kak_quoted_selections",
    "content": "'bar-baz'\n"
  },
  {
    "path": "test/normal/object/start/braces/cmd",
    "content": "[B\n"
  },
  {
    "path": "test/normal/object/start/braces/in",
    "content": "{\n  \"%(foo)\": \"bar\"\n}\n"
  },
  {
    "path": "test/normal/object/start/braces/kak_quoted_selections",
    "content": "'{\n  \"foo'\n"
  },
  {
    "path": "test/normal/object/start/brackets/cmd",
    "content": "[r\n"
  },
  {
    "path": "test/normal/object/start/brackets/in",
    "content": "[\n  \"%(foo)\"\n]\n"
  },
  {
    "path": "test/normal/object/start/brackets/kak_quoted_selections",
    "content": "'[\n  \"foo'\n"
  },
  {
    "path": "test/normal/object/start/double_quote/cmd",
    "content": "[Q\n"
  },
  {
    "path": "test/normal/object/start/double_quote/in",
    "content": "foo(\"%(b)ar\")\n"
  },
  {
    "path": "test/normal/object/start/double_quote/kak_quoted_selections",
    "content": "'\"b'\n"
  },
  {
    "path": "test/normal/object/start/grave_quote/cmd",
    "content": "[g\n"
  },
  {
    "path": "test/normal/object/start/grave_quote/in",
    "content": "echo `%(f)oo`\n"
  },
  {
    "path": "test/normal/object/start/grave_quote/kak_quoted_selections",
    "content": "'`f'\n"
  },
  {
    "path": "test/normal/object/start/indent/cmd",
    "content": "[i\n"
  },
  {
    "path": "test/normal/object/start/indent/in",
    "content": "\n  foo(%(b)ar)\n\n"
  },
  {
    "path": "test/normal/object/start/indent/kak_quoted_selections",
    "content": "'\n  foo(b'\n"
  },
  {
    "path": "test/normal/object/start/paragraph/count/cmd",
    "content": "2[p\n"
  },
  {
    "path": "test/normal/object/start/paragraph/count/in",
    "content": "a\n\nb\nc\n\nd\n%(e)\nf\n"
  },
  {
    "path": "test/normal/object/start/paragraph/count/kak_quoted_selections",
    "content": "'b\nc\n\nd\ne'\n"
  },
  {
    "path": "test/normal/object/start/paragraph/single/cmd",
    "content": "[p\n"
  },
  {
    "path": "test/normal/object/start/paragraph/single/in",
    "content": "a\nb\n\n%(c)\nd\n\ne\nf\n"
  },
  {
    "path": "test/normal/object/start/paragraph/single/kak_quoted_selections",
    "content": "'a\nb\n\n'\n"
  },
  {
    "path": "test/normal/object/start/parenthesis/cmd",
    "content": "[b\n"
  },
  {
    "path": "test/normal/object/start/parenthesis/in",
    "content": "foo(%(b)ar)\n"
  },
  {
    "path": "test/normal/object/start/parenthesis/kak_quoted_selections",
    "content": "'(b'\n"
  },
  {
    "path": "test/normal/object/start/sentence/count/cmd",
    "content": "2[s\n"
  },
  {
    "path": "test/normal/object/start/sentence/count/in",
    "content": "a b. c d. e %(f)\n"
  },
  {
    "path": "test/normal/object/start/sentence/count/kak_quoted_selections",
    "content": "'c d. e f'\n"
  },
  {
    "path": "test/normal/object/start/sentence/single/cmd",
    "content": "[s\n"
  },
  {
    "path": "test/normal/object/start/sentence/single/in",
    "content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et %(dolore magna aliqua).  Ut enim ad minim veniam, quis\nnostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore\neu fugiat nulla pariatur.  Excepteur sint occaecat cupidatat non proident,\nsunt in culpa qui officia deserunt mollit anim id est laborum.\n"
  },
  {
    "path": "test/normal/object/start/sentence/single/kak_quoted_selections",
    "content": "'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et dolore magna aliqua'\n"
  },
  {
    "path": "test/normal/object/start/sentence/to-buffer-begin/cmd",
    "content": "9[s\n"
  },
  {
    "path": "test/normal/object/start/sentence/to-buffer-begin/in",
    "content": "a b.\n\nc d. %(e) f.\n"
  },
  {
    "path": "test/normal/object/start/sentence/to-buffer-begin/kak_quoted_selections",
    "content": "'a b.\n\nc d.'\n"
  },
  {
    "path": "test/normal/object/start/single_quote/cmd",
    "content": "[q\n"
  },
  {
    "path": "test/normal/object/start/single_quote/in",
    "content": "foo('%(b)ar')\n"
  },
  {
    "path": "test/normal/object/start/single_quote/kak_quoted_selections",
    "content": "''\\''b'\n"
  },
  {
    "path": "test/normal/object/start/word/cmd",
    "content": "[w\n"
  },
  {
    "path": "test/normal/object/start/word/in",
    "content": "foo ba%(r) baz\n"
  },
  {
    "path": "test/normal/object/start/word/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/object/start-extending/angle/cmd",
    "content": "{a\n"
  },
  {
    "path": "test/normal/object/start-extending/angle/in",
    "content": "#include <%(f)oo>\n"
  },
  {
    "path": "test/normal/object/start-extending/angle/kak_quoted_selections",
    "content": "'<f'\n"
  },
  {
    "path": "test/normal/object/start-extending/argument/cmd",
    "content": "{u\n"
  },
  {
    "path": "test/normal/object/start-extending/argument/in",
    "content": "something(first, \"someth%(i)ng\", another);\n"
  },
  {
    "path": "test/normal/object/start-extending/argument/kak_quoted_selections",
    "content": "' \"somethi'\n"
  },
  {
    "path": "test/normal/object/start-extending/big-word/cmd",
    "content": "{<a-w>\n"
  },
  {
    "path": "test/normal/object/start-extending/big-word/in",
    "content": "foo bar-ba%(z) qux\n"
  },
  {
    "path": "test/normal/object/start-extending/big-word/kak_quoted_selections",
    "content": "'bar-baz'\n"
  },
  {
    "path": "test/normal/object/start-extending/braces/cmd",
    "content": "{B\n"
  },
  {
    "path": "test/normal/object/start-extending/braces/in",
    "content": "{\n  \"%(foo)\": \"bar\"\n}\n"
  },
  {
    "path": "test/normal/object/start-extending/braces/kak_quoted_selections",
    "content": "'{\n  \"f'\n"
  },
  {
    "path": "test/normal/object/start-extending/brackets/cmd",
    "content": "{r\n"
  },
  {
    "path": "test/normal/object/start-extending/brackets/in",
    "content": "[\n  \"%(foo)\"\n]\n"
  },
  {
    "path": "test/normal/object/start-extending/brackets/kak_quoted_selections",
    "content": "'[\n  \"f'\n"
  },
  {
    "path": "test/normal/object/start-extending/double_quote/cmd",
    "content": "{Q\n"
  },
  {
    "path": "test/normal/object/start-extending/double_quote/in",
    "content": "foo(\"%(b)ar\")\n"
  },
  {
    "path": "test/normal/object/start-extending/double_quote/kak_quoted_selections",
    "content": "'\"b'\n"
  },
  {
    "path": "test/normal/object/start-extending/grave_quote/cmd",
    "content": "{g\n"
  },
  {
    "path": "test/normal/object/start-extending/grave_quote/in",
    "content": "echo `%(f)oo`\n"
  },
  {
    "path": "test/normal/object/start-extending/grave_quote/kak_quoted_selections",
    "content": "'`f'\n"
  },
  {
    "path": "test/normal/object/start-extending/indent/cmd",
    "content": "{i\n"
  },
  {
    "path": "test/normal/object/start-extending/indent/in",
    "content": "\n  foo(%(b)ar)\n\n"
  },
  {
    "path": "test/normal/object/start-extending/indent/kak_quoted_selections",
    "content": "'\n  foo(b'\n"
  },
  {
    "path": "test/normal/object/start-extending/paragraph/count/cmd",
    "content": "<a-;>2{p\n"
  },
  {
    "path": "test/normal/object/start-extending/paragraph/count/in",
    "content": "a\n\nb\n\nc\n%(d\ne)\n"
  },
  {
    "path": "test/normal/object/start-extending/paragraph/count/kak_quoted_selections",
    "content": "'b\n\nc\nd\ne'\n"
  },
  {
    "path": "test/normal/object/start-extending/paragraph/single/cmd",
    "content": "{p\n"
  },
  {
    "path": "test/normal/object/start-extending/paragraph/single/in",
    "content": "a\nb\n\n%(c)\nd\n\ne\nf\n"
  },
  {
    "path": "test/normal/object/start-extending/paragraph/single/kak_quoted_selections",
    "content": "'a\nb\n\nc'\n"
  },
  {
    "path": "test/normal/object/start-extending/parenthesis/cmd",
    "content": "{b\n"
  },
  {
    "path": "test/normal/object/start-extending/parenthesis/in",
    "content": "foo(%(b)ar)\n"
  },
  {
    "path": "test/normal/object/start-extending/parenthesis/kak_quoted_selections",
    "content": "'(b'\n"
  },
  {
    "path": "test/normal/object/start-extending/sentence/count/cmd",
    "content": "<a-;>2{s\n"
  },
  {
    "path": "test/normal/object/start-extending/sentence/count/in",
    "content": "a b. c d. e %(f g)\n"
  },
  {
    "path": "test/normal/object/start-extending/sentence/count/kak_quoted_selections",
    "content": "'c d. e f g'\n"
  },
  {
    "path": "test/normal/object/start-extending/sentence/single/cmd",
    "content": "{s\n"
  },
  {
    "path": "test/normal/object/start-extending/sentence/single/in",
    "content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et %(dolore magna aliqua).  Ut enim ad minim veniam, quis\nnostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore\neu fugiat nulla pariatur.  Excepteur sint occaecat cupidatat non proident,\nsunt in culpa qui officia deserunt mollit anim id est laborum.\n"
  },
  {
    "path": "test/normal/object/start-extending/sentence/single/kak_quoted_selections",
    "content": "'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\nincididunt ut labore et d'\n"
  },
  {
    "path": "test/normal/object/start-extending/single_quote/cmd",
    "content": "{q\n"
  },
  {
    "path": "test/normal/object/start-extending/single_quote/in",
    "content": "foo('%(b)ar')\n"
  },
  {
    "path": "test/normal/object/start-extending/single_quote/kak_quoted_selections",
    "content": "''\\''b'\n"
  },
  {
    "path": "test/normal/object/start-extending/word/cmd",
    "content": "{w\n"
  },
  {
    "path": "test/normal/object/start-extending/word/in",
    "content": "foo ba%(r) baz\n"
  },
  {
    "path": "test/normal/object/start-extending/word/kak_quoted_selections",
    "content": "'bar'\n"
  },
  {
    "path": "test/normal/open-above/cmd",
    "content": "O\n"
  },
  {
    "path": "test/normal/open-above/in",
    "content": "foo\n"
  },
  {
    "path": "test/normal/open-above/out",
    "content": "\nfoo\n"
  },
  {
    "path": "test/normal/open-below/cmd",
    "content": "o\n"
  },
  {
    "path": "test/normal/open-below/in",
    "content": "foo\n"
  },
  {
    "path": "test/normal/open-below/out",
    "content": "foo\n\n"
  },
  {
    "path": "test/normal/open-multiple-above/cmd",
    "content": "3Obar<esc>\n"
  },
  {
    "path": "test/normal/open-multiple-above/in",
    "content": "foo\n"
  },
  {
    "path": "test/normal/open-multiple-above/out",
    "content": "bar\nbar\nbar\nfoo\n"
  },
  {
    "path": "test/normal/open-multiple-below/cmd",
    "content": "3obar<esc>\n"
  },
  {
    "path": "test/normal/open-multiple-below/in",
    "content": "foo\n"
  },
  {
    "path": "test/normal/open-multiple-below/out",
    "content": "foo\nbar\nbar\nbar\n"
  },
  {
    "path": "test/normal/paste-after/cmd",
    "content": "dp\n"
  },
  {
    "path": "test/normal/paste-after/in",
    "content": "-foo%(bar)-\n"
  },
  {
    "path": "test/normal/paste-after/out",
    "content": "-foo-bar\n"
  },
  {
    "path": "test/normal/paste-all-after/cmd",
    "content": "y<a-p>\n"
  },
  {
    "path": "test/normal/paste-all-after/in",
    "content": "-%(foo)-%(bar)-%(baz)-\n"
  },
  {
    "path": "test/normal/paste-all-after/out",
    "content": "-foofoobarbaz-barfoobarbaz-bazfoobarbaz-\n"
  },
  {
    "path": "test/normal/paste-all-before/cmd",
    "content": "y<a-P>\n"
  },
  {
    "path": "test/normal/paste-all-before/in",
    "content": "-%(foo)-%(bar)-%(baz)-\n"
  },
  {
    "path": "test/normal/paste-all-before/out",
    "content": "-foobarbazfoo-foobarbazbar-foobarbazbaz-\n"
  },
  {
    "path": "test/normal/paste-all-before-missing-newline/cmd",
    "content": "<a-P>\n"
  },
  {
    "path": "test/normal/paste-all-before-missing-newline/in",
    "content": "%(a)b%(c)\n"
  },
  {
    "path": "test/normal/paste-all-before-missing-newline/out",
    "content": "\n\ndab\n\ndc\n"
  },
  {
    "path": "test/normal/paste-all-before-missing-newline/rc",
    "content": "set-register dquote '\n' '\nd'\n"
  },
  {
    "path": "test/normal/paste-all-replace/cmd",
    "content": "y<a-R>\n"
  },
  {
    "path": "test/normal/paste-all-replace/in",
    "content": "-%(foo)-%(bar)-%(baz)-\n"
  },
  {
    "path": "test/normal/paste-all-replace/out",
    "content": "-foobarbaz-foobarbaz-foobarbaz-\n"
  },
  {
    "path": "test/normal/paste-before/cmd",
    "content": "dhP\n"
  },
  {
    "path": "test/normal/paste-before/in",
    "content": "-foo-%(bar)\n"
  },
  {
    "path": "test/normal/paste-before/out",
    "content": "-foobar-\n"
  },
  {
    "path": "test/normal/paste-before-multiple-selections/cmd",
    "content": "xSo<ret>dP\n"
  },
  {
    "path": "test/normal/paste-before-multiple-selections/in",
    "content": "foobar\n"
  },
  {
    "path": "test/normal/paste-before-multiple-selections/out",
    "content": "foobar\n\n"
  },
  {
    "path": "test/normal/pipe/cmd",
    "content": "|sed s/foo/bar/<ret>\n"
  },
  {
    "path": "test/normal/pipe/in",
    "content": "%(foo)\n"
  },
  {
    "path": "test/normal/pipe/out",
    "content": "bar\n"
  },
  {
    "path": "test/normal/pipe-reg/cmd",
    "content": "\"a|<ret>\n"
  },
  {
    "path": "test/normal/pipe-reg/in",
    "content": "%(foo)\n"
  },
  {
    "path": "test/normal/pipe-reg/out",
    "content": "bar\n"
  },
  {
    "path": "test/normal/pipe-reg/rc",
    "content": "set-register a 'sed s/foo/bar/'\n"
  },
  {
    "path": "test/normal/pipe-replaces-all-lines/cmd",
    "content": "%|printf '\\n\\n'<ret>\n"
  },
  {
    "path": "test/normal/pipe-replaces-all-lines/in",
    "content": "a\nb\n"
  },
  {
    "path": "test/normal/pipe-replaces-all-lines/out",
    "content": "\n\n"
  },
  {
    "path": "test/normal/pipe-to/cmd",
    "content": "%<a-|>sed -n \"s/foo/bar/; P\" > out<ret>:e! out<ret>\n"
  },
  {
    "path": "test/normal/pipe-to/in",
    "content": "foobar\n"
  },
  {
    "path": "test/normal/pipe-to/out",
    "content": "barbar\n"
  },
  {
    "path": "test/normal/pipe-to-reg/cmd",
    "content": "%\"a<a-|><ret>:e! out<ret>\n"
  },
  {
    "path": "test/normal/pipe-to-reg/in",
    "content": "foobar\n"
  },
  {
    "path": "test/normal/pipe-to-reg/out",
    "content": "barbar\n"
  },
  {
    "path": "test/normal/pipe-to-reg/rc",
    "content": "set-register a %{sed -n \"s/foo/bar/; P\" > out}\n"
  },
  {
    "path": "test/normal/previous-big-word/cmd",
    "content": "B\n"
  },
  {
    "path": "test/normal/previous-big-word/in",
    "content": "foo %(b)ar baz\n"
  },
  {
    "path": "test/normal/previous-big-word/kak_quoted_selections",
    "content": "'foo b'\n"
  },
  {
    "path": "test/normal/previous-big-word-extending/cmd",
    "content": "<a-B>\n"
  },
  {
    "path": "test/normal/previous-big-word-extending/in",
    "content": "foo %(b)ar baz\n"
  },
  {
    "path": "test/normal/previous-big-word-extending/kak_quoted_selections",
    "content": "'foo b'\n"
  },
  {
    "path": "test/normal/previous-word/cmd",
    "content": "b\n"
  },
  {
    "path": "test/normal/previous-word/in",
    "content": "foo %(b)ar baz\n"
  },
  {
    "path": "test/normal/previous-word/kak_quoted_selections",
    "content": "'foo '\n"
  },
  {
    "path": "test/normal/previous-word-extending/cmd",
    "content": "<a-b>\n"
  },
  {
    "path": "test/normal/previous-word-extending/in",
    "content": "foo %(b)ar baz\n"
  },
  {
    "path": "test/normal/previous-word-extending/kak_quoted_selections",
    "content": "'foo '\n"
  },
  {
    "path": "test/normal/previous-word-no-underscore/cmd",
    "content": "b\n"
  },
  {
    "path": "test/normal/previous-word-no-underscore/in",
    "content": "foo_%(b)ar\n"
  },
  {
    "path": "test/normal/previous-word-no-underscore/kak_quoted_selections",
    "content": "'_'\n"
  },
  {
    "path": "test/normal/previous-word-no-underscore/rc",
    "content": "set-option global extra_word_chars\n"
  },
  {
    "path": "test/normal/redo/cmd",
    "content": "duU\n"
  },
  {
    "path": "test/normal/redo/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/redo/out",
    "content": "foo  baz\n"
  },
  {
    "path": "test/normal/reload/cmd",
    "content": ":e!<ret>\n"
  },
  {
    "path": "test/normal/reload/in",
    "content": "blah\n"
  },
  {
    "path": "test/normal/reload/out",
    "content": "tchou\n"
  },
  {
    "path": "test/normal/reload/rc",
    "content": "set global autoreload no\nnop %sh{ echo tchou > $kak_buffile }\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert/cmd",
    "content": "ifoo<esc>.\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert/out",
    "content": "foofoo\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert-hooks/cmd",
    "content": ":hook -group h g InsertChar f %{exec FINSERTED}<ret>ifoo<esc>.\\.\\ifoo<esc>.\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert-hooks/out",
    "content": "fFINSERTEDoofFINSERTEDoofoofoofoo\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert-mapped/cmd",
    "content": "ixyz<esc>.\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert-mapped/out",
    "content": "zzxx\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert-mapped/rc",
    "content": "map global insert y '<a-;>gh'\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert-mapped-word-completion/cmd",
    "content": "ofo<c-x><c-w><tab><esc>.\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert-mapped-word-completion/in",
    "content": "foo\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert-mapped-word-completion/out",
    "content": "foo\nfoo\nfoo\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-insert-mapped-word-completion/rc",
    "content": "map global insert <tab> <c-n>\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-normal-exec/cmd",
    "content": "i<a-;>:execute-keys foo<ret><esc>.\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-normal-exec/out",
    "content": "foofoo\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-normal-movement/cmd",
    "content": "ifoo<a-;>ghbar<esc>.\n"
  },
  {
    "path": "test/normal/repeat-insert/repeat-normal-movement/out",
    "content": "barbarfoofoo\n"
  },
  {
    "path": "test/normal/repeat-select/repeat-end-paragraph/cmd",
    "content": "]p<a-.>\n"
  },
  {
    "path": "test/normal/repeat-select/repeat-end-paragraph/in",
    "content": "this is\nthe first\nparagraph\n\nthis is the\nsecond one\n"
  },
  {
    "path": "test/normal/repeat-select/repeat-end-paragraph/kak_selections_desc",
    "content": "5.1,6.11\n"
  },
  {
    "path": "test/normal/repeat-select/repeat-find-char/cmd",
    "content": "fl<a-.>\n"
  },
  {
    "path": "test/normal/repeat-select/repeat-find-char/in",
    "content": "this is the first line\nthis is the second line\n"
  },
  {
    "path": "test/normal/repeat-select/repeat-find-char/kak_selections_desc",
    "content": "1.19,2.20\n"
  },
  {
    "path": "test/normal/replace/cmd",
    "content": "rb\n"
  },
  {
    "path": "test/normal/replace/in",
    "content": "a\n"
  },
  {
    "path": "test/normal/replace/out",
    "content": "b\n"
  },
  {
    "path": "test/normal/replace-lines/cmd",
    "content": "ey%<a-s>R\n"
  },
  {
    "path": "test/normal/replace-lines/in",
    "content": "line 1\nline 2\nline 3\nline 4\n"
  },
  {
    "path": "test/normal/replace-lines/out",
    "content": "linelinelineline\n"
  },
  {
    "path": "test/normal/replay-complex-insert/cmd",
    "content": "i(<a-;>e<right>)<esc>fw;.\n"
  },
  {
    "path": "test/normal/replay-complex-insert/in",
    "content": "word word\n"
  },
  {
    "path": "test/normal/replay-complex-insert/out",
    "content": "(word) (word)\n"
  },
  {
    "path": "test/normal/replay-macro/cmd",
    "content": "q\n"
  },
  {
    "path": "test/normal/replay-macro/out",
    "content": "mawww\n"
  },
  {
    "path": "test/normal/replay-macro/rc",
    "content": "reg @ imawww\n"
  },
  {
    "path": "test/normal/restore-selections/cmd",
    "content": "Z<space>z\n"
  },
  {
    "path": "test/normal/restore-selections/in",
    "content": "%(foo) %(bar) %(baz)\n"
  },
  {
    "path": "test/normal/restore-selections/kak_quoted_selections",
    "content": "'foo' 'bar' 'baz'\n"
  },
  {
    "path": "test/normal/rotate/cmd",
    "content": ")<a-,>\n"
  },
  {
    "path": "test/normal/rotate/in",
    "content": "%(foo) %(bar) %(baz)\n"
  },
  {
    "path": "test/normal/rotate/kak_quoted_selections",
    "content": "'bar' 'baz'\n"
  },
  {
    "path": "test/normal/rotate-content/cmd",
    "content": "<a-)>\n"
  },
  {
    "path": "test/normal/rotate-content/in",
    "content": "%(foo) %(bar) %(baz)\n"
  },
  {
    "path": "test/normal/rotate-content/out",
    "content": "baz foo bar\n"
  },
  {
    "path": "test/normal/save-selections/cmd",
    "content": "Zgez\n"
  },
  {
    "path": "test/normal/save-selections/in",
    "content": "this is a %(multiple lines) file\nwith different contents\n"
  },
  {
    "path": "test/normal/save-selections/kak_quoted_selections",
    "content": "'multiple lines'\n"
  },
  {
    "path": "test/normal/search/cmd",
    "content": "/[a-z]+<ret>\n"
  },
  {
    "path": "test/normal/search/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/search/kak_quoted_selections",
    "content": "'baz'\n"
  },
  {
    "path": "test/normal/search-extending/cmd",
    "content": "?[a-z]+<ret>\n"
  },
  {
    "path": "test/normal/search-extending/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/search-extending/kak_quoted_selections",
    "content": "'bar baz'\n"
  },
  {
    "path": "test/normal/search-extending-multiple-selections/cmd",
    "content": "CC?3<ret>\n"
  },
  {
    "path": "test/normal/search-extending-multiple-selections/in",
    "content": "1\n2\n3\n4\n"
  },
  {
    "path": "test/normal/search-extending-multiple-selections/kak_selections_desc",
    "content": "2.1,3.1 1.1,3.1\n"
  },
  {
    "path": "test/normal/search-reverse/cmd",
    "content": "<a-/>[a-z]+<ret>\n"
  },
  {
    "path": "test/normal/search-reverse/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/search-reverse/kak_quoted_selections",
    "content": "'foo'\n"
  },
  {
    "path": "test/normal/search-reverse-extending/cmd",
    "content": "<a-?>[a-z]+<ret>\n"
  },
  {
    "path": "test/normal/search-reverse-extending/in",
    "content": "foo %(bar) baz\n"
  },
  {
    "path": "test/normal/search-reverse-extending/kak_quoted_selections",
    "content": "'foo b'\n"
  },
  {
    "path": "test/normal/search-reverse-extending-multiple-selections/cmd",
    "content": "CCC)<a-?>1<ret>\n"
  },
  {
    "path": "test/normal/search-reverse-extending-multiple-selections/in",
    "content": "1\n1\n1\n1\n"
  },
  {
    "path": "test/normal/search-reverse-extending-multiple-selections/kak_selections_desc",
    "content": "2.1,1.1 3.1,2.1 4.1,3.1\n"
  },
  {
    "path": "test/normal/search-reverse-rightmost/missed-match/cmd",
    "content": "<a-/>(ab|baba)<ret>\n"
  },
  {
    "path": "test/normal/search-reverse-rightmost/missed-match/in",
    "content": "ababa%(b)\n"
  },
  {
    "path": "test/normal/search-reverse-rightmost/missed-match/kak_quoted_selections",
    "content": "'baba'\n"
  },
  {
    "path": "test/normal/search-reverse-rightmost/overlap/cmd",
    "content": "<a-/>...<ret>\n"
  },
  {
    "path": "test/normal/search-reverse-rightmost/overlap/in",
    "content": "abcdefg%(h)\n"
  },
  {
    "path": "test/normal/search-reverse-rightmost/overlap/kak_quoted_selections",
    "content": "'efg'\n"
  },
  {
    "path": "test/normal/select/cmd",
    "content": "sfoo<ret>\n"
  },
  {
    "path": "test/normal/select/in",
    "content": "%(foo bar)\n"
  },
  {
    "path": "test/normal/select/kak_quoted_selections",
    "content": "'foo'\n"
  },
  {
    "path": "test/normal/select-horizontal-whitespace/cmd",
    "content": "s\\h<ret>\n"
  },
  {
    "path": "test/normal/select-horizontal-whitespace/in",
    "content": "Should select all characters to the right of\nthe colon except U+000B (vertical tab)\n%(\nU+0009:\t\nU+000B:\u000b\nU+000C:\f\nU+0020: \nU+00A0: \nU+1680: \nU+2000: \nU+2001: \nU+2002: \nU+2003: \nU+2004: \nU+2005: \nU+2006: \nU+2007: \nU+2008: \nU+2009: \nU+200A: \nU+2028: \nU+2029: \nU+202F: \nU+205F: \nU+3000:　\nU+FEFF:﻿\n)\n"
  },
  {
    "path": "test/normal/select-horizontal-whitespace/kak_quoted_selections",
    "content": "'\t' '\f' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '　' '﻿'\n"
  },
  {
    "path": "test/normal/select-line/cmd",
    "content": "x\n"
  },
  {
    "path": "test/normal/select-line/in",
    "content": "foo\n"
  },
  {
    "path": "test/normal/select-line/kak_quoted_selections",
    "content": "'foo\n'\n"
  },
  {
    "path": "test/normal/selection-undo/fold-redundant-entries/cmd",
    "content": "\n"
  },
  {
    "path": "test/normal/selection-undo/fold-redundant-entries/in",
    "content": "1\n2\n3\n4\n"
  },
  {
    "path": "test/normal/selection-undo/fold-redundant-entries/out",
    "content": "2\n3\nhere4\n"
  },
  {
    "path": "test/normal/selection-undo/fold-redundant-entries/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"gjgkxd<a-u>ihere<esc>\" ] }'\n"
  },
  {
    "path": "test/normal/selection-undo/redo/cmd",
    "content": "\n"
  },
  {
    "path": "test/normal/selection-undo/redo/in",
    "content": "1\n2\n3\n4\n"
  },
  {
    "path": "test/normal/selection-undo/redo/out",
    "content": "1\n2\nhere3\n4\n"
  },
  {
    "path": "test/normal/selection-undo/redo/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"2jj<a-u><a-u><a-U>ihere<esc>\" ] }'\n"
  },
  {
    "path": "test/normal/selection-undo/undo/cmd",
    "content": "\n"
  },
  {
    "path": "test/normal/selection-undo/undo/in",
    "content": "1\n2\n3\n4\n"
  },
  {
    "path": "test/normal/selection-undo/undo/out",
    "content": "here1\n2\n3\n4\n"
  },
  {
    "path": "test/normal/selection-undo/undo/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"j2j<a-u><a-u>ihere<esc>\" ] }'\n"
  },
  {
    "path": "test/normal/selection-undo/windisplay-hook/cmd",
    "content": "\n"
  },
  {
    "path": "test/normal/selection-undo/windisplay-hook/in",
    "content": "1\n2\n3\n"
  },
  {
    "path": "test/normal/selection-undo/windisplay-hook/out",
    "content": "1\n2\nhere3\n"
  },
  {
    "path": "test/normal/selection-undo/windisplay-hook/rc",
    "content": "hook global WinDisplay .*/out %{exec j}\n"
  },
  {
    "path": "test/normal/selection-undo/windisplay-hook/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"j:buffer *debug*<ret><a-u>ihere<esc>\" ] }'\n"
  },
  {
    "path": "test/normal/split/cmd",
    "content": "S <ret>\n"
  },
  {
    "path": "test/normal/split/in",
    "content": "%(foo bar baz)\n"
  },
  {
    "path": "test/normal/split/kak_quoted_selections",
    "content": "'foo' 'bar' 'baz'\n"
  },
  {
    "path": "test/normal/split-at-begin/cmd",
    "content": "%S\\w+<ret>\n"
  },
  {
    "path": "test/normal/split-at-begin/in",
    "content": "foo bar baz\n"
  },
  {
    "path": "test/normal/split-at-begin/kak_quoted_selections",
    "content": "' ' ' ' '\n'\n"
  },
  {
    "path": "test/normal/split-multiple-lines/cmd",
    "content": "%2<a-s>\n"
  },
  {
    "path": "test/normal/split-multiple-lines/in",
    "content": "line 1\nline 2\nline 3\nline 4\nline 5\n"
  },
  {
    "path": "test/normal/split-multiple-lines/kak_quoted_selections",
    "content": "'line 1\nline 2\n' 'line 3\nline 4\n' 'line 5\n'\n"
  },
  {
    "path": "test/normal/switch-case/cmd",
    "content": "<a-`>\n"
  },
  {
    "path": "test/normal/switch-case/in",
    "content": "%(foo BAR)\n"
  },
  {
    "path": "test/normal/switch-case/kak_quoted_selections",
    "content": "'FOO bar'\n"
  },
  {
    "path": "test/normal/to-char-backward/cmd",
    "content": "<a-f>|\n"
  },
  {
    "path": "test/normal/to-char-backward/in",
    "content": "foo|%(bar)|baz\n"
  },
  {
    "path": "test/normal/to-char-backward/kak_quoted_selections",
    "content": "'|bar'\n"
  },
  {
    "path": "test/normal/to-char-backward-extending/cmd",
    "content": "<a-F>|\n"
  },
  {
    "path": "test/normal/to-char-backward-extending/in",
    "content": "foo|%(bar)|baz\n"
  },
  {
    "path": "test/normal/to-char-backward-extending/kak_quoted_selections",
    "content": "'|b'\n"
  },
  {
    "path": "test/normal/to-char-forward/cmd",
    "content": "f|\n"
  },
  {
    "path": "test/normal/to-char-forward/in",
    "content": "foo|%(bar)|baz\n"
  },
  {
    "path": "test/normal/to-char-forward/kak_quoted_selections",
    "content": "'r|'\n"
  },
  {
    "path": "test/normal/to-char-forward-extending/cmd",
    "content": "F|\n"
  },
  {
    "path": "test/normal/to-char-forward-extending/in",
    "content": "foo|%(bar)|baz\n"
  },
  {
    "path": "test/normal/to-char-forward-extending/kak_quoted_selections",
    "content": "'bar|'\n"
  },
  {
    "path": "test/normal/trim/cmd",
    "content": "%<a-s>_<a-)>\n"
  },
  {
    "path": "test/normal/trim/in",
    "content": "line 1\n  line 2\n    line 3\n"
  },
  {
    "path": "test/normal/trim/out",
    "content": "line 3\n  line 1\n    line 2\n"
  },
  {
    "path": "test/normal/trim/table/cmd",
    "content": "_\n"
  },
  {
    "path": "test/normal/trim/table/in",
    "content": "|       |       |        |                |\n|:------|------:|:------:|----------------|\n|%(foo )|%( bar)|%( qux )|%( pop )%( hip )|\n%(   kakoune   )\n"
  },
  {
    "path": "test/normal/trim/table/kak_quoted_selections",
    "content": "'foo' 'bar' 'qux' 'pop' 'hip' 'kakoune'\n"
  },
  {
    "path": "test/normal/trim-drop-empty/cmd",
    "content": "%<a-s>_\n"
  },
  {
    "path": "test/normal/trim-drop-empty/in",
    "content": " foo \n\n    \nbar \n"
  },
  {
    "path": "test/normal/trim-drop-empty/kak_quoted_selections",
    "content": "'foo' 'bar'\n"
  },
  {
    "path": "test/normal/trim-lines/cmd",
    "content": "<a-x>\n"
  },
  {
    "path": "test/normal/trim-lines/in",
    "content": "one %(line\ntwo lines\nthree) lines\n"
  },
  {
    "path": "test/normal/trim-lines/kak_quoted_selections",
    "content": "'two lines\n'\n"
  },
  {
    "path": "test/normal/undo/cmd",
    "content": "du\n"
  },
  {
    "path": "test/normal/undo/in",
    "content": "%(foo)\n"
  },
  {
    "path": "test/normal/undo/out",
    "content": "foo\n"
  },
  {
    "path": "test/normal/undo-after-replace-lines/cmd",
    "content": "ey%<a-s>RuU\n"
  },
  {
    "path": "test/normal/undo-after-replace-lines/in",
    "content": "line 1\nline 2\nline 3\nline 4\n"
  },
  {
    "path": "test/normal/undo-after-replace-lines/out",
    "content": "linelinelineline\n"
  },
  {
    "path": "test/normal/upper-case/cmd",
    "content": "~\n"
  },
  {
    "path": "test/normal/upper-case/in",
    "content": "%(foo)\n"
  },
  {
    "path": "test/normal/upper-case/kak_quoted_selections",
    "content": "'FOO'\n"
  },
  {
    "path": "test/normal/user-modes/lock/cmd",
    "content": "<a-,>fff<esc>f\n"
  },
  {
    "path": "test/normal/user-modes/lock/in",
    "content": "123delete\n"
  },
  {
    "path": "test/normal/user-modes/lock/out",
    "content": "delete\n"
  },
  {
    "path": "test/normal/user-modes/lock/rc",
    "content": "declare-user-mode foo\nmap global foo f d\nmap global normal <a-,> ':enter-user-mode -lock foo<ret>'\n"
  },
  {
    "path": "test/normal/user-modes/once/cmd",
    "content": "<a-,>ff\n"
  },
  {
    "path": "test/normal/user-modes/once/in",
    "content": "bar\n"
  },
  {
    "path": "test/normal/user-modes/once/out",
    "content": "hello from foo\n"
  },
  {
    "path": "test/normal/user-modes/once/rc",
    "content": "declare-user-mode foo\nmap global foo f 'wchello from foo<esc>'\nmap global normal <a-,> ':enter-user-mode foo<ret>'\n"
  },
  {
    "path": "test/normal/yank/cmd",
    "content": "ya<c-r>\"\n"
  },
  {
    "path": "test/normal/yank/in",
    "content": "%(foo)\n"
  },
  {
    "path": "test/normal/yank/out",
    "content": "foofoo\n"
  },
  {
    "path": "test/prompt/history-abort-previous/cmd",
    "content": "\n"
  },
  {
    "path": "test/prompt/history-abort-previous/out",
    "content": "bazbarfoob\n"
  },
  {
    "path": "test/prompt/history-abort-previous/rc",
    "content": "define-command append -params 1 %{ set-register dquote %arg{1}; execute-keys A<c-r>\"<esc> }\n"
  },
  {
    "path": "test/prompt/history-abort-previous/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":append baz<ret>:append bar<ret>:append foo<ret>:append b<c-p><c-n><ret>\" ] }'\n"
  },
  {
    "path": "test/prompt/history-mapped-keys/cmd",
    "content": ""
  },
  {
    "path": "test/prompt/history-mapped-keys/kak_reg_colon",
    "content": "nop hello\n"
  },
  {
    "path": "test/prompt/history-mapped-keys/rc",
    "content": "map global normal <c-1> %{:nop }\nmap global prompt <c-2> %{hello}\nmap global prompt <c-j> <ret>\n"
  },
  {
    "path": "test/prompt/history-mapped-keys/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"<c-1><c-2><c-j>\" ] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [false] }'\n"
  },
  {
    "path": "test/prompt/history-navigate/cmd",
    "content": "\n"
  },
  {
    "path": "test/prompt/history-navigate/out",
    "content": "foobarbazbar\n"
  },
  {
    "path": "test/prompt/history-navigate/rc",
    "content": "define-command append -params 1 %{ set-register dquote %arg{1}; execute-keys A<c-r>\"<esc> }\n"
  },
  {
    "path": "test/prompt/history-navigate/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":append foo<ret>:append bar<ret>:append baz<ret>:<c-p><c-p><c-p><c-n><c-n><c-p><ret>\" ] }'\n"
  },
  {
    "path": "test/prompt/history-previous/cmd",
    "content": "\n"
  },
  {
    "path": "test/prompt/history-previous/out",
    "content": "foobarbazbar\n"
  },
  {
    "path": "test/prompt/history-previous/rc",
    "content": "define-command append -params 1 %{ set-register dquote %arg{1}; execute-keys A<c-r>\"<esc> }\n"
  },
  {
    "path": "test/prompt/history-previous/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":append foo<ret>:append bar<ret>:append baz<ret>:<c-p><c-p><c-p><c-n><ret>\" ] }'\n"
  },
  {
    "path": "test/prompt/history-previous-prefix/cmd",
    "content": "\n"
  },
  {
    "path": "test/prompt/history-previous-prefix/out",
    "content": "bazbarfoobar\n"
  },
  {
    "path": "test/prompt/history-previous-prefix/rc",
    "content": "define-command append -params 1 %{ set-register dquote %arg{1}; execute-keys A<c-r>\"<esc> }\n"
  },
  {
    "path": "test/prompt/history-previous-prefix/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":append baz<ret>:append bar<ret>:append foo<ret>:append b<c-p><ret>\" ] }'\n"
  },
  {
    "path": "test/prompt/history-repeat-last/cmd",
    "content": "\n"
  },
  {
    "path": "test/prompt/history-repeat-last/out",
    "content": "foofoo\n"
  },
  {
    "path": "test/prompt/history-repeat-last/rc",
    "content": "define-command test %{ execute-keys Afoo<esc> }\n"
  },
  {
    "path": "test/prompt/history-repeat-last/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":test<ret>:<ret>\" ] }'\n"
  },
  {
    "path": "test/prompt/history-stop-at-first/cmd",
    "content": "\n"
  },
  {
    "path": "test/prompt/history-stop-at-first/out",
    "content": "foobarfoo\n"
  },
  {
    "path": "test/prompt/history-stop-at-first/rc",
    "content": "define-command append -params 1 %{ set-register dquote %arg{1}; execute-keys A<c-r>\"<esc> }\n"
  },
  {
    "path": "test/prompt/history-stop-at-first/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":append foo<ret>:append bar<ret>:<c-p><c-p><c-p><ret>\" ] }'\n"
  },
  {
    "path": "test/regression/0-assert-on-itersel/cmd",
    "content": ":exec -itersel jj<ret>\n"
  },
  {
    "path": "test/regression/0-assert-on-itersel/in",
    "content": "line %(1)\n%(l)ine 2\nline 3\n"
  },
  {
    "path": "test/regression/0-assert-on-itersel/kak_quoted_selections",
    "content": "'l' '3'\n"
  },
  {
    "path": "test/regression/0-autocomplete-overrules-completers/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-autocomplete-overrules-completers/in",
    "content": "\n\n\n\n"
  },
  {
    "path": "test/regression/0-autocomplete-overrules-completers/out",
    "content": "\na2\na3\n./ui-in\n"
  },
  {
    "path": "test/regression/0-autocomplete-overrules-completers/rc",
    "content": "set-option global autocomplete prompt\ndeclare-option -hidden completions line1_completions\ndeclare-option -hidden completions line2_completions\ndeclare-option -hidden completions line3_completions\nset-option global completers option=line1_completions option=line2_completions option=line3_completions\nset-option global line1_completions \"1.1+0@%val(timestamp)\" \"a1||a1\"\nset-option global line2_completions \"2.1+0@%val(timestamp)\" \"a2||a2\"\nset-option global line3_completions \"3.1+0@%sh{echo $(($kak_timestamp+1))}\" \"a3||a3\"\n"
  },
  {
    "path": "test/regression/0-autocomplete-overrules-completers/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"i\" ] }'\nsleep .2 # trigger insert completion auto update\n# Implicit completion can be toggled with <c-o>.\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"<esc>ji<c-o><c-n><esc>\" ] }'\n# Implicit completion can be toggled with <c-n>.\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"ji<c-n><esc>\" ] }'\n# Explicit completion still works.\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"ji./ui-<c-x>f<c-n>\" ] }'\n\n"
  },
  {
    "path": "test/regression/0-comment-after-command/cmd",
    "content": ":my-command<ret>\n"
  },
  {
    "path": "test/regression/0-comment-after-command/out",
    "content": "foo\n"
  },
  {
    "path": "test/regression/0-comment-after-command/rc",
    "content": "def my-command %{\n    buffer-next # go to next\n    %sh{ echo buffer-previous }\n    exec ifoo<esc>\n}\n"
  },
  {
    "path": "test/regression/0-compute-modified-range-crash/cmd",
    "content": "es.<ret>!echo<ret>u\n"
  },
  {
    "path": "test/regression/0-compute-modified-range-crash/in",
    "content": "aha\n"
  },
  {
    "path": "test/regression/0-compute-modified-range-crash/out",
    "content": "aha\n"
  },
  {
    "path": "test/regression/0-crash-on-0-height-window/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-crash-on-0-height-window/in",
    "content": "1\n2\n3\n"
  },
  {
    "path": "test/regression/0-crash-on-0-height-window/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"3\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"resize\", \"params\": [ 0, 1] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/0-crash-on-BufSetOption-hook/cmd",
    "content": ":db<ret>:e out<ret>\n"
  },
  {
    "path": "test/regression/0-crash-on-BufSetOption-hook/rc",
    "content": "declare-option str foo\nhook global BufSetOption 'indentwidth=(\\d+)' %{set buffer foo bar}\n"
  },
  {
    "path": "test/regression/0-crash-on-backspace-at-buffer-begin/cmd",
    "content": "i<backspace><esc>\n"
  },
  {
    "path": "test/regression/0-crash-on-delete-buffer-BufClose/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-crash-on-delete-buffer-BufClose/rc",
    "content": "edit -scratch buf1\nedit -scratch buf2\nhook buffer BufClose buf2 %{\n    delete-buffer\n    delete-buffer buf1\n}\ndelete-buffer\n"
  },
  {
    "path": "test/regression/0-crash-on-pipe-with-selection-access/cmd",
    "content": "%<a-s>|[ $kak_selection = \"bar\" ] && echo \"yes\"<ret><a-+>\n"
  },
  {
    "path": "test/regression/0-crash-on-pipe-with-selection-access/in",
    "content": "foo\nbar\nbaz\n"
  },
  {
    "path": "test/regression/0-crash-on-pipe-with-selection-access/kak_quoted_selections",
    "content": "'yes\n'\n"
  },
  {
    "path": "test/regression/0-crash-on-pipe-with-selection-access/out",
    "content": "yes\n"
  },
  {
    "path": "test/regression/0-crash-on-regex-prompt-change/cmd",
    "content": "%5<lt><a-s><a-h><semicolon>?\\S<ret>s<space>\n"
  },
  {
    "path": "test/regression/0-crash-on-regex-prompt-change/in",
    "content": "          (\n            cd foobar\n"
  },
  {
    "path": "test/regression/0-crash-on-regex-prompt-change/rc",
    "content": "set-option global indentwidth 2\n"
  },
  {
    "path": "test/regression/0-crash-on-regex-prompt-change/script",
    "content": "ui_out -ignore 9\n"
  },
  {
    "path": "test/regression/0-crash-on-specialy-crafted-modeline/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-crash-on-specialy-crafted-modeline/rc",
    "content": "set global modelinefmt %{\\}\n"
  },
  {
    "path": "test/regression/0-crash-on-tab-just-before-wrap-column/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-crash-on-tab-just-before-wrap-column/in",
    "content": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\twrapped\n"
  },
  {
    "path": "test/regression/0-crash-on-tab-just-before-wrap-column/rc",
    "content": "addhl global/ wrap\n"
  },
  {
    "path": "test/regression/0-crash-on-vertical-move-with-invalid-utf8-at-eol/cmd",
    "content": "xj\n"
  },
  {
    "path": "test/regression/0-crash-on-vertical-move-with-invalid-utf8-at-eol/in",
    "content": "xxx\n\n"
  },
  {
    "path": "test/regression/0-deindent-on-mixed-indent-line/cmd",
    "content": "<lt>\n"
  },
  {
    "path": "test/regression/0-deindent-on-mixed-indent-line/in",
    "content": "\t    fo%(o)\n"
  },
  {
    "path": "test/regression/0-deindent-on-mixed-indent-line/out",
    "content": "    foo\n"
  },
  {
    "path": "test/regression/0-delete-buffer-while-dragging/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-delete-buffer-while-dragging/script",
    "content": "ui_out -ignore 7\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"mouse_press\", \"params\": [ \"left\", 0, 0] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":delete-buffer<ret>\" ] }'\n"
  },
  {
    "path": "test/regression/0-eval-creates-prompt/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-eval-creates-prompt/script",
    "content": "ui_out -ignore 7\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":eval %{ exec %{:echo -} }<ret>\" ] }'\nui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"markup 123<ret>\" ] }'\nui_out -until-grep '\"method\": \"draw_status\", .* \"contents\": \"123\"'\n"
  },
  {
    "path": "test/regression/0-intermediate-regex-saved-in-history/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-intermediate-regex-saved-in-history/in",
    "content": "foo bar\n"
  },
  {
    "path": "test/regression/0-intermediate-regex-saved-in-history/kak_selection",
    "content": "foo\n"
  },
  {
    "path": "test/regression/0-intermediate-regex-saved-in-history/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"/foo<ret>/b\" ] }'\nsleep 0.1\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"ar<ret>/<up><up><ret>\" ] }'\nui_out -ignore 1\n"
  },
  {
    "path": "test/regression/0-mouse-during-insert/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-mouse-during-insert/in",
    "content": "123\n"
  },
  {
    "path": "test/regression/0-mouse-during-insert/out",
    "content": "a1b2c3\n"
  },
  {
    "path": "test/regression/0-mouse-during-insert/script",
    "content": "ui_out -ignore 6\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"i\" ] }'\nui_out -ignore 2\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"a\" ] }'\nui_out -ignore 3\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"mouse_press\", \"params\": [ \"left\", 0, 2] }'\nui_out -ignore 3\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"b\" ] }'\nui_out -ignore 3\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"mouse_release\", \"params\": [ \"left\", 0, 4] }'\nui_out -ignore 3\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"c<esc>\" ] }'\nui_out -ignore 3\n"
  },
  {
    "path": "test/regression/0-no-incsearch/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-no-incsearch/in",
    "content": "\nx1\nx2\n"
  },
  {
    "path": "test/regression/0-no-incsearch/kak_selection",
    "content": "x1\n"
  },
  {
    "path": "test/regression/0-no-incsearch/rc",
    "content": "set-option global incsearch false\n"
  },
  {
    "path": "test/regression/0-no-incsearch/script",
    "content": "ui_out -ignore 4\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"/x\" ] }'\nsleep 0.1\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \".<ret>\" ] }'\nsleep 0.1\nui_out -ignore 1\n"
  },
  {
    "path": "test/regression/0-nothing-selected-on-prompt-initial-shift-tab/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/0-nothing-selected-on-prompt-initial-shift-tab/out",
    "content": "ccc\n"
  },
  {
    "path": "test/regression/0-nothing-selected-on-prompt-initial-shift-tab/rc",
    "content": "set global autocomplete prompt\ndef my-command -params 0..1 -shell-script-candidates %{ printf \"aaa\\nbbb\\nccc\" } %{ exec i %arg{1} <esc> }\n"
  },
  {
    "path": "test/regression/0-nothing-selected-on-prompt-initial-shift-tab/script",
    "content": "ui_out -ignore 7\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":my-command \" ] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_show\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"aaa\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bbb\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ccc\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"prompt\"] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"<s-tab><ret>\" ] }'\n"
  },
  {
    "path": "test/regression/0-open-below-should-not-move-cursor-on-eol/cmd",
    "content": "x;:exec -draft o<ret>\n"
  },
  {
    "path": "test/regression/0-open-below-should-not-move-cursor-on-eol/in",
    "content": "foo\n"
  },
  {
    "path": "test/regression/0-open-below-should-not-move-cursor-on-eol/kak_selections_desc",
    "content": "1.4,1.4\n"
  },
  {
    "path": "test/regression/0-open-below-should-not-move-cursor-on-eol/out",
    "content": "foo\n\n"
  },
  {
    "path": "test/regression/0-replace-last-eol-with-eol/cmd",
    "content": "yjR\n"
  },
  {
    "path": "test/regression/0-replace-last-eol-with-eol/in",
    "content": "\n\n"
  },
  {
    "path": "test/regression/0-replace-last-eol-with-eol/out",
    "content": "\n\n"
  },
  {
    "path": "test/regression/0-rotate-at-eof-mutates-selection/cmd",
    "content": "%<a-s><a-)><a-)>\n"
  },
  {
    "path": "test/regression/0-rotate-at-eof-mutates-selection/in",
    "content": "foo\nbar\nbaz\n"
  },
  {
    "path": "test/regression/0-rotate-at-eof-mutates-selection/out",
    "content": "bar\nbaz\nfoo\n"
  },
  {
    "path": "test/regression/0-select-object-with-empty-begin-match/cmd",
    "content": "<a-i>c^,m<ret>\n"
  },
  {
    "path": "test/regression/0-select-object-with-empty-begin-match/in",
    "content": "\nWhat is L%(o)rem Ipsum?\n\nWhat is Lorem %(I)psum?\n\nWhat is Lorem Ipsum%(?)\n"
  },
  {
    "path": "test/regression/0-select-object-with-empty-begin-match/kak_selections_desc",
    "content": "3.1,4.18 2.1,2.12\n"
  },
  {
    "path": "test/regression/0-slow-BufCloseFifo/cmd",
    "content": ":run<ret>\n"
  },
  {
    "path": "test/regression/0-slow-BufCloseFifo/rc",
    "content": "define-command run %{\n    evaluate-commands %sh{\n        mkfifo fifo1 fifo2 2>/dev/null\n        ( : >fifo1 & ) > /dev/null 2>&1 </dev/null\n    }\n    edit! -fifo fifo1 *fifo*\n    add-highlighter global/myhl regex foo 0:green\n    hook -once global BufCloseFifo .* %{\n        evaluate-commands -client client0 %{\n            nop %sh{sleep 2}\n        }\n        hook -once buffer NormalIdle .* %{\n            echo -to-file fifo2 still alive\n        }\n    }\n}\n"
  },
  {
    "path": "test/regression/0-slow-BufCloseFifo/script",
    "content": "mkfifo fifo2 2>/dev/null\nassert_eq \"$(cat fifo2)\" \"still alive\"\n"
  },
  {
    "path": "test/regression/0-spurious-undo-group-on-external/cmd",
    "content": "Abarbaz<esc>u\n"
  },
  {
    "path": "test/regression/0-spurious-undo-group-on-external/in",
    "content": "foo\n"
  },
  {
    "path": "test/regression/0-spurious-undo-group-on-external/out",
    "content": "foo\n"
  },
  {
    "path": "test/regression/0-spurious-undo-group-on-external/rc",
    "content": "decl int my_option\nhook global InsertChar r %{ eval -buffer %val{buffile} %{ eval \"set buffer my_option 1\" } }\n"
  },
  {
    "path": "test/regression/0-undo-change-at-eof/cmd",
    "content": "<a-l>yjxRu\n"
  },
  {
    "path": "test/regression/0-undo-change-at-eof/in",
    "content": "copy\npaste\n"
  },
  {
    "path": "test/regression/0-undo-change-at-eof/out",
    "content": "copy\npaste\n"
  },
  {
    "path": "test/regression/1014-ambiguous-kak_selections/cmd",
    "content": "%<a-s>H\n"
  },
  {
    "path": "test/regression/1014-ambiguous-kak_selections/in",
    "content": "foo\\:bar\nfoo\\\\\nbar\n"
  },
  {
    "path": "test/regression/1014-ambiguous-kak_selections/kak_quoted_selections",
    "content": "'foo\\:bar' 'foo\\\\' 'bar'\n"
  },
  {
    "path": "test/regression/1051-crash-on-empty-param/cmd",
    "content": ":echo -debug %opt{ui_options}<ret>\n"
  },
  {
    "path": "test/regression/1053-crash-on-deletion-and-paste/cmd",
    "content": "xSo<ret>dp\n"
  },
  {
    "path": "test/regression/1053-crash-on-deletion-and-paste/in",
    "content": "foobar\n"
  },
  {
    "path": "test/regression/1053-crash-on-deletion-and-paste/out",
    "content": "of\no\nbar\n"
  },
  {
    "path": "test/regression/1074-comment-leader-autoinsert-error/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/regression/1074-comment-leader-autoinsert-error/in",
    "content": "// abc%( )def\n"
  },
  {
    "path": "test/regression/1074-comment-leader-autoinsert-error/out",
    "content": "// abc\n// def\n"
  },
  {
    "path": "test/regression/1074-comment-leader-autoinsert-error/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/regression/1105-object-selection-behaviour/cmd",
    "content": "<a-i>'\n"
  },
  {
    "path": "test/regression/1105-object-selection-behaviour/in",
    "content": "print 'aaa'; print %(')bbb'; print 'ccc';\n"
  },
  {
    "path": "test/regression/1105-object-selection-behaviour/kak_quoted_selections",
    "content": "'bbb'\n"
  },
  {
    "path": "test/regression/1111-unexpected-behaviour-on-new-line-in-comment/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/regression/1111-unexpected-behaviour-on-new-line-in-comment/in",
    "content": "// test te%(s)t\n// foo bar\n"
  },
  {
    "path": "test/regression/1111-unexpected-behaviour-on-new-line-in-comment/out",
    "content": "// test te\n// st\n// foo bar\n"
  },
  {
    "path": "test/regression/1111-unexpected-behaviour-on-new-line-in-comment/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/regression/1118-misaligned-comment-start/cmd",
    "content": "c<ret><esc>\n"
  },
  {
    "path": "test/regression/1118-misaligned-comment-start/in",
    "content": "    // abc%( )def\n"
  },
  {
    "path": "test/regression/1118-misaligned-comment-start/out",
    "content": "    // abc\n    // def\n"
  },
  {
    "path": "test/regression/1118-misaligned-comment-start/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/regression/1129-capture-groups-are-broken/cmd",
    "content": "xs(\\w+)<ret>i<c-r>1<esc>\n"
  },
  {
    "path": "test/regression/1129-capture-groups-are-broken/in",
    "content": "foo bar\n"
  },
  {
    "path": "test/regression/1129-capture-groups-are-broken/out",
    "content": "foofoo barbar\n"
  },
  {
    "path": "test/regression/1195-infinite-loop-in-regex-matching/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/1195-infinite-loop-in-regex-matching/in",
    "content": "g\u001f\r`)kW!l\u0003)v\tR\u000fjWW\u0005q/.h3bάOa\u0013=~Nuù{G\u001dq\u0019T\u001dYFf#\u001d\u000eB-\u0007\tw]<\u001c\rc\u00137JٻGsjImt\r7q\u0013\r\u0010\u00152j\u00153=\u000b\u0003\u0002U\u0002*S\u000391;=MMJ\u0012^r>eUt\u0002U\u0001,qswUy3͊]A\u001c3i\u000e\u0003\u0011ْ.\u000f\u001dnDD\u0011$:r8f\u0003b;\u001bz\rfJ\n"
  },
  {
    "path": "test/regression/1195-infinite-loop-in-regex-matching/out",
    "content": "g\u001f\r`)kW!l\u0003)v\tR\u000fjWW\u0005q/.h3bάOa\u0013=~Nuù{G\u001dq\u0019T\u001dYFf#\u001d\u000eB-\u0007\tw]<\u001c\rc\u00137JٻGsjImt\r7q\u0013\r\u0010\u00152j\u00153=\u000b\u0003\u0002U\u0002*S\u000391;=MMJ\u0012^r>eUt\u0002U\u0001,qswUy3͊]A\u001c3i\u000e\u0003\u0011ْ.\u000f\u001dnDD\u0011$:r8f\u0003b;\u001bz\rfJ\n"
  },
  {
    "path": "test/regression/1195-infinite-loop-in-regex-matching/rc",
    "content": "add-highlighter window/sh regions\nadd-highlighter window/sh/code default-region group\nadd-highlighter window/sh/heredoc region -match-capture '<<-?(\\w+)' '^\\t*(\\w+)$' group\n"
  },
  {
    "path": "test/regression/1227-segfault-on-option-access/cmd",
    "content": ":test<ret>\n"
  },
  {
    "path": "test/regression/1227-segfault-on-option-access/out",
    "content": "k\n"
  },
  {
    "path": "test/regression/1227-segfault-on-option-access/rc",
    "content": "decl str _\n\nhook global BufCreate \\*test\\* %{\n    hook buffer BufSetOption _=.+ 'exec \"i%opt{_}\"'\n}\n\ndef test %{\n    edit -scratch *test*\n    set buffer=*test* _ \"k\"\n}\n"
  },
  {
    "path": "test/regression/1233-corrent-cursor-handling-for-indent-objects/cmd",
    "content": "]i\n"
  },
  {
    "path": "test/regression/1233-corrent-cursor-handling-for-indent-objects/in",
    "content": "    foo\n    %(b)ar\n    baz\n"
  },
  {
    "path": "test/regression/1233-corrent-cursor-handling-for-indent-objects/kak_quoted_selections",
    "content": "'bar\n    baz\n'\n"
  },
  {
    "path": "test/regression/1275-replaced-range-split/cmd",
    "content": ":addhl window/ show-whitespaces<ret>i<space><esc>:addhl window/ column 1 red<ret>A<backspace><esc>\n"
  },
  {
    "path": "test/regression/1308-wrong-behaviour-of-kak-indent-on-newline/cmd",
    "content": "o\n"
  },
  {
    "path": "test/regression/1308-wrong-behaviour-of-kak-indent-on-newline/in",
    "content": "# Comment\n"
  },
  {
    "path": "test/regression/1308-wrong-behaviour-of-kak-indent-on-newline/kak_quoted_selections",
    "content": "'\n'\n"
  },
  {
    "path": "test/regression/1308-wrong-behaviour-of-kak-indent-on-newline/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/kakrc.kak\"\nset buffer filetype kak\n"
  },
  {
    "path": "test/regression/1382-column-highlighter-broken-on-horizontal-scroll/cmd",
    "content": "gl\n"
  },
  {
    "path": "test/regression/1382-column-highlighter-broken-on-horizontal-scroll/in",
    "content": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxx\txxxx\nxxxxxxxx\n\n"
  },
  {
    "path": "test/regression/1382-column-highlighter-broken-on-horizontal-scroll/rc",
    "content": "add-highlighter window/ column 20 default,blue\n"
  },
  {
    "path": "test/regression/1382-column-highlighter-broken-on-horizontal-scroll/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"xxxxxxxxxx\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"x\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"x\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"xxx\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"      \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"          \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"          \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }]], { \"line\": 0, \"column\": 79 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/1433-scrolloff-broken-with-soft-wrap/cmd",
    "content": "22jgl\n"
  },
  {
    "path": "test/regression/1433-scrolloff-broken-with-soft-wrap/in",
    "content": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23 -----------------------------------------------------------------------------wrap\n24\n25\n26\n"
  },
  {
    "path": "test/regression/1433-scrolloff-broken-with-soft-wrap/rc",
    "content": "add-highlighter window/ wrap\nset global scrolloff 1,0\n"
  },
  {
    "path": "test/regression/1433-scrolloff-broken-with-soft-wrap/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"3\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"6\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"7\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"8\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"9\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23 -----------------------------------------------------------------------------\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"wra\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"p\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }]], { \"line\": 22, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 23:84 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/1435-misplaced-cursor-with-show_matching-hl/cmd",
    "content": "O<space><a-;><c-l><esc>\n"
  },
  {
    "path": "test/regression/1435-misplaced-cursor-with-show_matching-hl/rc",
    "content": "add-highlighter window/ show-whitespaces\n"
  },
  {
    "path": "test/regression/1435-misplaced-cursor-with-show_matching-hl/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"·\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }]], { \"line\": 0, \"column\": 1 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:2 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[+]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"·\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }]], { \"line\": 0, \"column\": 1 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:2 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[+]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\n"
  },
  {
    "path": "test/regression/1453-show_whitespaces-highlighter-breaks-tab-alignment/cmd",
    "content": "ithis<tab>is<tab>a<tab>test<esc>\n"
  },
  {
    "path": "test/regression/1453-show_whitespaces-highlighter-breaks-tab-alignment/rc",
    "content": "add-highlighter window/ show-whitespaces\n"
  },
  {
    "path": "test/regression/1453-show_whitespaces-highlighter-breaks-tab-alignment/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"this\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"→   \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"is\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"→     \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"→      \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"test\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }]], { \"line\": 0, \"column\": 28 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:15 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[+]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/1459-assertion-on-wrapped-long-line/cmd",
    "content": "ge\n"
  },
  {
    "path": "test/regression/1459-assertion-on-wrapped-long-line/in",
    "content": "Eius voluptas ut blanditiis tempore quia harum quae nobis. Eum magnam natus quo unde animi voluptatem laudantium. Repellat quidem animi omnis laudantium ut sed. A vero omnis autem qui qui. Maxime qui quo voluptatem vel id similique est doloribus. Exercitationem tenetur eos sint rerum eos et. Quia dolor esse distinctio eveniet voluptas sed ut. Cupiditate doloremque ut cupiditate nulla possimus. Dolorem natus autem aspernatur et placeat laudantium molestiae laudantium. Accusamus ea labore asperiores dignissimos explicabo ab culpa. Suscipit aliquam voluptas fuga voluptas animi. Perferendis eaque aut et iusto libero et quod. Ipsa maxime animi in libero in similique aliquam repudiandae. Totam omnis facere consequatur praesentium sapiente. Voluptas et deserunt repellendus velit. Veritatis est tempore a qui nihil harum ea qui. Voluptatum sequi deleniti saepe. Quae facere sunt quam esse est voluptatem qui dolorem. Et aut ut porro blanditiis impedit minus quia. Reiciendis optio quas eius veritatis. Delectus ea ipsum aut aspernatur. Iure corporis quisquam nisi in aspernatur ipsam et aliquam. Voluptatum facilis deserunt veritatis nobis. Alias fuga voluptatem eum ea ullam blanditiis quam. Nam omnis magni quaerat officia non dolor voluptatem. Eius et ut quia iusto occaecati saepe eos. Ut et esse quas quas. Aut sunt porro similique aut. Et veniam repellendus blanditiis necessitatibus provident fugit perspiciatis cupiditate. Cum voluptas dicta necessitatibus ut id nihil nihil. Similique asperiores atque neque perspiciatis. Iusto ea et recusandae dolorum ut totam. Quis quibusdam quo suscipit nihil molestiae porro aut. Quas reprehenderit dolorum omnis libero ipsa sapiente ipsam maxime. Quae repudiandae nulla aut eos unde. Architecto qui neque quis quis dolores voluptatem est. Nobis asperiores molestias accusantium molestias soluta. Voluptatem quod temporibus itaque minima. Non quos amet ab consequuntur qui. Dolorem laudantium dicta placeat necessitatibus qui. Molestias laboriosam ut quis non odio incidunt quod optio. Vel qui molestiae ipsum et sunt rem voluptatibus. Pariatur voluptatem officia mollitia harum ut. Ipsa eum dolor quo. Rerum ullam beatae doloribus unde.\n"
  },
  {
    "path": "test/regression/1459-assertion-on-wrapped-long-line/out",
    "content": "Eius voluptas ut blanditiis tempore quia harum quae nobis. Eum magnam natus quo unde animi voluptatem laudantium. Repellat quidem animi omnis laudantium ut sed. A vero omnis autem qui qui. Maxime qui quo voluptatem vel id similique est doloribus. Exercitationem tenetur eos sint rerum eos et. Quia dolor esse distinctio eveniet voluptas sed ut. Cupiditate doloremque ut cupiditate nulla possimus. Dolorem natus autem aspernatur et placeat laudantium molestiae laudantium. Accusamus ea labore asperiores dignissimos explicabo ab culpa. Suscipit aliquam voluptas fuga voluptas animi. Perferendis eaque aut et iusto libero et quod. Ipsa maxime animi in libero in similique aliquam repudiandae. Totam omnis facere consequatur praesentium sapiente. Voluptas et deserunt repellendus velit. Veritatis est tempore a qui nihil harum ea qui. Voluptatum sequi deleniti saepe. Quae facere sunt quam esse est voluptatem qui dolorem. Et aut ut porro blanditiis impedit minus quia. Reiciendis optio quas eius veritatis. Delectus ea ipsum aut aspernatur. Iure corporis quisquam nisi in aspernatur ipsam et aliquam. Voluptatum facilis deserunt veritatis nobis. Alias fuga voluptatem eum ea ullam blanditiis quam. Nam omnis magni quaerat officia non dolor voluptatem. Eius et ut quia iusto occaecati saepe eos. Ut et esse quas quas. Aut sunt porro similique aut. Et veniam repellendus blanditiis necessitatibus provident fugit perspiciatis cupiditate. Cum voluptas dicta necessitatibus ut id nihil nihil. Similique asperiores atque neque perspiciatis. Iusto ea et recusandae dolorum ut totam. Quis quibusdam quo suscipit nihil molestiae porro aut. Quas reprehenderit dolorum omnis libero ipsa sapiente ipsam maxime. Quae repudiandae nulla aut eos unde. Architecto qui neque quis quis dolores voluptatem est. Nobis asperiores molestias accusantium molestias soluta. Voluptatem quod temporibus itaque minima. Non quos amet ab consequuntur qui. Dolorem laudantium dicta placeat necessitatibus qui. Molestias laboriosam ut quis non odio incidunt quod optio. Vel qui molestiae ipsum et sunt rem voluptatibus. Pariatur voluptatem officia mollitia harum ut. Ipsa eum dolor quo. Rerum ullam beatae doloribus unde.\n"
  },
  {
    "path": "test/regression/1459-assertion-on-wrapped-long-line/rc",
    "content": "add-highlighter window/ wrap\n"
  },
  {
    "path": "test/regression/1469-assert-on-repeat-insert/cmd",
    "content": "i<a-;>!echo tchou<ret><a-;>.\n"
  },
  {
    "path": "test/regression/1469-assert-on-repeat-insert/error",
    "content": "'exec': repeating last insert not available in this context\n"
  },
  {
    "path": "test/regression/1504-assertion-on-incorrect-pipe-use/cmd",
    "content": "2o<esc>|echo>&2<ret><a-+>\n"
  },
  {
    "path": "test/regression/1504-assertion-on-incorrect-pipe-use/kak_selections_desc",
    "content": "1.1,1.1\n"
  },
  {
    "path": "test/regression/1518-wrong-undo-handling-with-fifo-buffers/cmd",
    "content": "<a-O>ifoo<c-u><esc>:edit -fifo /dev/null <c-r>%<ret>u\n"
  },
  {
    "path": "test/regression/1518-wrong-undo-handling-with-fifo-buffers/error",
    "content": "'exec': nothing left to undo\n"
  },
  {
    "path": "test/regression/1525-lua-indent-error/cmd",
    "content": "c<ret>foo()<esc>\n"
  },
  {
    "path": "test/regression/1525-lua-indent-error/in",
    "content": "bugdemo = {}\nfunction bugdemo.bug()%( )\n"
  },
  {
    "path": "test/regression/1525-lua-indent-error/out",
    "content": "bugdemo = {}\nfunction bugdemo.bug()\n    foo()\nend\n"
  },
  {
    "path": "test/regression/1525-lua-indent-error/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/lua.kak\"\nset buffer filetype lua\n"
  },
  {
    "path": "test/regression/1580-A-not-moving-to-eol/cmd",
    "content": "i<ret>\n"
  },
  {
    "path": "test/regression/1580-A-not-moving-to-eol/in",
    "content": "1234 {\n%(})\n"
  },
  {
    "path": "test/regression/1580-A-not-moving-to-eol/kak_selections_desc",
    "content": "2.5,2.5\n"
  },
  {
    "path": "test/regression/1580-A-not-moving-to-eol/rc",
    "content": "hook global InsertChar \\n %{ exec -draft k<a-gt>; exec <esc>kA; }\n"
  },
  {
    "path": "test/regression/1680-crash-with-dot-and-alt-semicolon/cmd",
    "content": "afoo<a-;>:w<ret><esc>.\n"
  },
  {
    "path": "test/regression/1680-crash-with-dot-and-alt-semicolon/out",
    "content": "\nfoofoo\n"
  },
  {
    "path": "test/regression/1731-wrap-hidden-buffer/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/1731-wrap-hidden-buffer/in",
    "content": "01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  \n\n02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02  \n\n"
  },
  {
    "path": "test/regression/1731-wrap-hidden-buffer/rc",
    "content": "add-highlighter window/ wrap\n"
  },
  {
    "path": "test/regression/1731-wrap-hidden-buffer/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 0\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 0\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 0\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 0\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 0\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 0\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 0\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/1741-scrolloff-hides-end-of-lines/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/1741-scrolloff-hides-end-of-lines/in",
    "content": "01234567890123456789012345678901234567890123456789012345678901234567890123456789\n"
  },
  {
    "path": "test/regression/1741-scrolloff-hides-end-of-lines/rc",
    "content": "set global scrolloff 0,20\n"
  },
  {
    "path": "test/regression/1741-scrolloff-hides-end-of-lines/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1234567890123456789012345678901234567890123456789012345678901234567890123456789\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/1829-unicode-as-string-delimiter/cmd",
    "content": ":exec i %•abc• <lt>esc> <ret>\n"
  },
  {
    "path": "test/regression/1829-unicode-as-string-delimiter/out",
    "content": "abc\n"
  },
  {
    "path": "test/regression/1902-regex-lookbehind-ignore-before-current-pos/cmd",
    "content": "/(?<lt>=X)X<ret>n\n"
  },
  {
    "path": "test/regression/1902-regex-lookbehind-ignore-before-current-pos/in",
    "content": "XXX\n"
  },
  {
    "path": "test/regression/1902-regex-lookbehind-ignore-before-current-pos/kak_selections_desc",
    "content": "1.3,1.3\n"
  },
  {
    "path": "test/regression/1904-select-empty-line-indent-gets-whole-buffer/cmd",
    "content": "2j<a-i>i\n"
  },
  {
    "path": "test/regression/1904-select-empty-line-indent-gets-whole-buffer/in",
    "content": "foo():\n    foo\n\n    bar\n"
  },
  {
    "path": "test/regression/1904-select-empty-line-indent-gets-whole-buffer/kak_quoted_selections",
    "content": "'    foo\n\n    bar\n'\n"
  },
  {
    "path": "test/regression/1920-crash-on-python-in-docstring/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/1920-crash-on-python-in-docstring/in",
    "content": "\"\"\">>> \"\"\"\n"
  },
  {
    "path": "test/regression/1920-crash-on-python-in-docstring/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/regression/1920-crash-on-python-in-docstring/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\"] }, \"contents\": \"\\\"\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\\"\\\">>> \\\"\\\"\\\"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/1937-opening-missing-file-by-full-path-fails/cmd",
    "content": "\"aP\n"
  },
  {
    "path": "test/regression/1937-opening-missing-file-by-full-path-fails/out",
    "content": "/kakoune-inexisting-directory/missing-file\n"
  },
  {
    "path": "test/regression/1937-opening-missing-file-by-full-path-fails/rc",
    "content": "eval -draft %{\n    edit /kakoune-inexisting-directory/missing-file\n    reg a %val{buffile}\n}\n"
  },
  {
    "path": "test/regression/1964-O-does-not-position-cursor-correctly/cmd",
    "content": "Oqux<esc>\n"
  },
  {
    "path": "test/regression/1964-O-does-not-position-cursor-correctly/in",
    "content": "%(foo\nbar\nbaz)\n"
  },
  {
    "path": "test/regression/1964-O-does-not-position-cursor-correctly/out",
    "content": "qux\nfoo\nbar\nbaz\n"
  },
  {
    "path": "test/regression/2030-custom-selection-nesting/cmd",
    "content": "<a-a>caa,cc<ret><a-a>caa,cc<ret>\n"
  },
  {
    "path": "test/regression/2030-custom-selection-nesting/in",
    "content": "aa aa %(bb) cc cc\n"
  },
  {
    "path": "test/regression/2030-custom-selection-nesting/kak_quoted_selections",
    "content": "'aa aa bb cc cc'\n"
  },
  {
    "path": "test/regression/2056-assert-on-small-window/cmd",
    "content": "<c-l>\n"
  },
  {
    "path": "test/regression/2056-assert-on-small-window/in",
    "content": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"
  },
  {
    "path": "test/regression/2056-assert-on-small-window/rc",
    "content": "add-highlighter window/ number-lines\n"
  },
  {
    "path": "test/regression/2056-assert-on-small-window/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"resize\", \"params\": [ 5, 2] }'\n"
  },
  {
    "path": "test/regression/2078-assert-on-restoring-invalid-selections/cmd",
    "content": "ifoo<esc>ibar<esc>:set-register ^ <c-r>%@1@0 100.100,100.100<ret>z\n"
  },
  {
    "path": "test/regression/2078-assert-on-restoring-invalid-selections/kak_selections_desc",
    "content": "1.7,1.7\n"
  },
  {
    "path": "test/regression/2129-inside-parentheses/cmd",
    "content": "<a-i>b\n"
  },
  {
    "path": "test/regression/2129-inside-parentheses/in",
    "content": "((a+b)%(/)(a-b))\n"
  },
  {
    "path": "test/regression/2129-inside-parentheses/kak_quoted_selections",
    "content": "'(a+b)/(a-b)'\n"
  },
  {
    "path": "test/regression/2133-assert-on-rotate-contents/cmd",
    "content": "2<a-)>\n"
  },
  {
    "path": "test/regression/2133-assert-on-rotate-contents/in",
    "content": "%(foo) %(bar) %(baz)\n"
  },
  {
    "path": "test/regression/2133-assert-on-rotate-contents/out",
    "content": "bar foo baz\n"
  },
  {
    "path": "test/regression/2245-wrap-long-word/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/2245-wrap-long-word/in",
    "content": "  short line\n\n  looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line\n  looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line\n"
  },
  {
    "path": "test/regression/2245-wrap-long-word/rc",
    "content": "add-highlighter global/ wrap -word -indent\n"
  },
  {
    "path": "test/regression/2245-wrap-long-word/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" short line\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"  looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"  \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" line\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"  looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"  \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ng line\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/2367-surround-with-tight-nesting/cmd",
    "content": "<a-a>r\n"
  },
  {
    "path": "test/regression/2367-surround-with-tight-nesting/in",
    "content": "%([)[a]b] [[c]d%(])\n"
  },
  {
    "path": "test/regression/2367-surround-with-tight-nesting/kak_quoted_selections",
    "content": "'[[a]b]' '[[c]d]'\n"
  },
  {
    "path": "test/regression/2420-discrepancy-in-star-behaviour/cmd",
    "content": "x*n\n"
  },
  {
    "path": "test/regression/2420-discrepancy-in-star-behaviour/in",
    "content": "a\n\n%(a)\n"
  },
  {
    "path": "test/regression/2420-discrepancy-in-star-behaviour/kak_selections_desc",
    "content": "1.1,1.2\n"
  },
  {
    "path": "test/regression/2499-html-regions-assert/cmd",
    "content": "jjjJJ<a-x><c-l>d<c-l>\n"
  },
  {
    "path": "test/regression/2499-html-regions-assert/in",
    "content": "<html>\n\t<head></head>\n\t<body>\n\t\t<div>\n\t\t\t<input name=\"bar\"/>\n\t\t</div>\n\n\t\t<div id=\"foo\" class=\"bar\">\n\t\t\tTest 1\n\t\t</div>\n\n\t\t<div id=\"baz\" class=\"fiz\">\n\t\t\tTest 2\n\t\t</div>\n\t</body>\n</html>\n"
  },
  {
    "path": "test/regression/2499-html-regions-assert/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/html.kak\"\nset buffer filetype html\n"
  },
  {
    "path": "test/regression/2499-html-regions-assert/script",
    "content": "ui_out -ignore 18 # prevent deadlock on Mac OS\n"
  },
  {
    "path": "test/regression/2562-column-highlighter-bleeding/cmd",
    "content": ""
  },
  {
    "path": "test/regression/2562-column-highlighter-bleeding/rc",
    "content": "add-highlighter window/ column 81 default,red\n"
  },
  {
    "path": "test/regression/2562-column-highlighter-bleeding/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/2711-weird-regex-highlighter-behaviour/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/2711-weird-regex-highlighter-behaviour/in",
    "content": "foo.bar();\n"
  },
  {
    "path": "test/regression/2711-weird-regex-highlighter-behaviour/rc",
    "content": "add-highlighter buffer/ regex ((?<!\\.\\.)(?<=\\.)|(?<=->))[a-zA-Z](\\w+)?\\b(?![>\"\\(]) 0:red\n"
  },
  {
    "path": "test/regression/2711-weird-regex-highlighter-behaviour/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"f\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"oo.bar();\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/2737-segfault-on-WinDisplay-hook/cmd",
    "content": ":e foo<ret>\n"
  },
  {
    "path": "test/regression/2737-segfault-on-WinDisplay-hook/out",
    "content": "\n"
  },
  {
    "path": "test/regression/2737-segfault-on-WinDisplay-hook/rc",
    "content": "hook global WinDisplay .* %{ evaluate-commands %sh{\n    tmp=$(mktemp -d)\n    fifo=\"${tmp}/fifo\"\n    mkfifo $fifo\n    printf \"%s\\n\" \"try %{ delete-buffer *test* }\n                   edit! -fifo ${fifo} *test*\n                   try %{ hook -always global KakEnd .* %{ nop %sh{ rm -rf ${tmp} } } }\n                   map buffer normal '<ret>' '<a-h>;/:<c-v><c-i><ret><a-h>2<s-l><a-l>:<space>test-jump $kak_bufname<ret>'\"\n}}\n"
  },
  {
    "path": "test/regression/2755-segfault-on-double-wrap-highlighters/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/2755-segfault-on-double-wrap-highlighters/in",
    "content": "If you're not reporting a bug (e.g. requesting a feature or asking a general question), feel free to remove this template.\nOtherwise, please edit the following sections with the relevant information ().\n"
  },
  {
    "path": "test/regression/2755-segfault-on-double-wrap-highlighters/rc",
    "content": "add-highlighter buffer/regions regions\nadd-highlighter buffer/regions/inline default-region regions\n\nadd-highlighter buffer/ wrap\nadd-highlighter buffer/ wrap -indent\n"
  },
  {
    "path": "test/regression/2861-backspace-merges-selections/cmd",
    "content": "a<backspace>oo\n"
  },
  {
    "path": "test/regression/2861-backspace-merges-selections/in",
    "content": "%(fo)%(fo)%(fo)\n"
  },
  {
    "path": "test/regression/2861-backspace-merges-selections/out",
    "content": "foofoofoo\n"
  },
  {
    "path": "test/regression/2999-buggy-wrapping/cmd",
    "content": "jvt\n"
  },
  {
    "path": "test/regression/2999-buggy-wrapping/in",
    "content": "const lowToUP = {0x0061: 0x0041, 0x0062: 0x0042, 0x0063: 0x0043, 0x0064: 0x0044, 0x0065: 0x0045, 0x0066: 0x0046, 0x0067: 0x0047, 0x0068: 0x0048, 0x0069: 0x0049, 0x006A: 0x004A, 0x006B: 0x004B, 0x006C: 0x004C, 0x006D: 0x004D, 0x006E: 0x004E, 0x006F: 0x004F, 0x0070: 0x0050, 0x0071: 0x0051, 0x0072: 0x0052, 0x0073: 0x0053, 0x0074: 0x0054, 0x0075: 0x0055, 0x0076: 0x0056, 0x0077: 0x0057, 0x0078: 0x0058, 0x0079: 0x0059, 0x007A: 0x005A, 0x00E0: 0x00C0, 0x00E1: 0x00C1, 0x00E2: 0x00C2, 0x00E3: 0x00C3, 0x00E4: 0x00C4, 0x00E5: 0x00C5, 0x00E6: 0x00C6, 0x00E7: 0x00C7, 0x00E8: 0x00C8, 0x00E9: 0x00C9, 0x00EA: 0x00CA, 0x00EB: 0x00CB, 0x00EC: 0x00CC, 0x00ED: 0x00CD, 0x00EE: 0x00CE, 0x00EF: 0x00CF, 0x00F0: 0x00D0, 0x00F1: 0x00D1, 0x00F2: 0x00D2, 0x00F3: 0x00D3, 0x00F4: 0x00D4, 0x00F5: 0x00D5, 0x00F6: 0x00D6, 0x00F8: 0x00D8, 0x00F9: 0x00D9, 0x00FA: 0x00DA, 0x00FB: 0x00DB, 0x00FC: 0x00DC, 0x00FD: 0x00DD, 0x00FE: 0x00DE, 0x00FF: 0x0178, 0x0101: 0x0100, 0x0103: 0x0102, 0x0105: 0x0104, 0x0107: 0x0106, 0x0109: 0x0108, 0x010B: 0x010A, 0x010D: 0x010C, 0x010F: 0x010E, 0x0111: 0x0110, 0x0113: 0x0112, 0x0115: 0x0114, 0x0117: 0x0116, 0x0119: 0x0118, 0x011B: 0x011A, 0x011D: 0x011C, 0x011F: 0x011E, 0x0121: 0x0120, 0x0123: 0x0122, 0x0125: 0x0124, 0x0127: 0x0126, 0x0129: 0x0128, 0x012B: 0x012A, 0x012D: 0x012C, 0x012F: 0x012E, 0x0131: 0x0049, 0x0133: 0x0132, 0x0135: 0x0134, 0x0137: 0x0136, 0x013A: 0x0139, 0x013C: 0x013B, 0x013E: 0x013D, 0x0140: 0x013F, 0x0142: 0x0141, 0x0144: 0x0143, 0x0146: 0x0145, 0x0148: 0x0147, 0x014B: 0x014A, 0x014D: 0x014C, 0x014F: 0x014E, 0x0151: 0x0150, 0x0153: 0x0152, 0x0155: 0x0154, 0x0157: 0x0156, 0x0159: 0x0158, 0x015B: 0x015A, 0x015D: 0x015C, 0x015F: 0x015E, 0x0161: 0x0160, 0x0163: 0x0162, 0x0165: 0x0164, 0x0167: 0x0166, 0x0169: 0x0168, 0x016B: 0x016A, 0x016D: 0x016C, 0x016F: 0x016E, 0x0171: 0x0170, 0x0173: 0x0172, 0x0175: 0x0174, 0x0177: 0x0176, 0x017A: 0x0179, 0x017C: 0x017B, 0x017E: 0x017D, 0x0183: 0x0182, 0x0185: 0x0184, 0x0188: 0x0187, 0x018C: 0x018B, 0x0192: 0x0191, 0x0199: 0x0198, 0x01A1: 0x01A0, 0x01A3: 0x01A2, 0x01A5: 0x01A4, 0x01A8: 0x01A7, 0x01AD: 0x01AC, 0x01B0: 0x01AF, 0x01B4: 0x01B3, 0x01B6: 0x01B5, 0x01B9: 0x01B8, 0x01BD: 0x01BC, 0x01C6: 0x01C4, 0x01C9: 0x01C7, 0x01CC: 0x01CA, 0x01CE: 0x01CD, 0x01D0: 0x01CF, 0x01D2: 0x01D1, 0x01D4: 0x01D3, 0x01D6: 0x01D5, 0x01D8: 0x01D7, 0x01DA: 0x01D9, 0x01DC: 0x01DB, 0x01DF: 0x01DE, 0x01E1: 0x01E0, 0x01E3: 0x01E2, 0x01E5: 0x01E4, 0x01E7: 0x01E6, 0x01E9: 0x01E8, 0x01EB: 0x01EA, 0x01ED: 0x01EC, 0x01EF: 0x01EE, 0x01F3: 0x01F1, 0x01F5: 0x01F4, 0x01FB: 0x01FA, 0x01FD: 0x01FC, 0x01FF: 0x01FE, 0x0201: 0x0200, 0x0203: 0x0202, 0x0205: 0x0204, 0x0207: 0x0206, 0x0209: 0x0208, 0x020B: 0x020A, 0x020D: 0x020C, 0x020F: 0x020E, 0x0211: 0x0210, 0x0213: 0x0212, 0x0215: 0x0214, 0x0217: 0x0216, 0x0253: 0x0181, 0x0254: 0x0186, 0x0257: 0x018A, 0x0258: 0x018E, 0x0259: 0x018F, 0x025B: 0x0190, 0x0260: 0x0193, 0x0263: 0x0194, 0x0268: 0x0197, 0x0269: 0x0196, 0x026F: 0x019C, 0x0272: 0x019D, 0x0275: 0x019F, 0x0283: 0x01A9, 0x0288: 0x01AE, 0x028A: 0x01B1, 0x028B: 0x01B2, 0x0292: 0x01B7, 0x03AC: 0x0386, 0x03AD: 0x0388, 0x03AE: 0x0389, 0x03AF: 0x038A, 0x03B1: 0x0391, 0x03B2: 0x0392, 0x03B3: 0x0393, 0x03B4: 0x0394, 0x03B5: 0x0395, 0x03B6: 0x0396, 0x03B7: 0x0397, 0x03B8: 0x0398, 0x03B9: 0x0399, 0x03BA: 0x039A, 0x03BB: 0x039B, 0x03BC: 0x039C, 0x03BD: 0x039D, 0x03BE: 0x039E, 0x03BF: 0x039F, 0x03C0: 0x03A0, 0x03C1: 0x03A1, 0x03C3: 0x03A3, 0x03C4: 0x03A4, 0x03C5: 0x03A5, 0x03C6: 0x03A6, 0x03C7: 0x03A7, 0x03C8: 0x03A8, 0x03C9: 0x03A9, 0x03CA: 0x03AA, 0x03CB: 0x03AB, 0x03CC: 0x038C, 0x03CD: 0x038E, 0x03CE: 0x038F, 0x03E3: 0x03E2, 0x03E5: 0x03E4, 0x03E7: 0x03E6, 0x03E9: 0x03E8, 0x03EB: 0x03EA, 0x03ED: 0x03EC, 0x03EF: 0x03EE, 0x0430: 0x0410, 0x0431: 0x0411, 0x0432: 0x0412, 0x0433: 0x0413, 0x0434: 0x0414, 0x0435: 0x0415, 0x0436: 0x0416, 0x0437: 0x0417, 0x0438: 0x0418, 0x0439: 0x0419, 0x043A: 0x041A, 0x043B: 0x041B, 0x043C: 0x041C, 0x043D: 0x041D, 0x043E: 0x041E, 0x043F: 0x041F, 0x0440: 0x0420, 0x0441: 0x0421, 0x0442: 0x0422, 0x0443: 0x0423, 0x0444: 0x0424, 0x0445: 0x0425, 0x0446: 0x0426, 0x0447: 0x0427, 0x0448: 0x0428, 0x0449: 0x0429, 0x044A: 0x042A, 0x044B: 0x042B, 0x044C: 0x042C, 0x044D: 0x042D, 0x044E: 0x042E, 0x044F: 0x042F, 0x0451: 0x0401, 0x0452: 0x0402, 0x0453: 0x0403, 0x0454: 0x0404, 0x0455: 0x0405, 0x0456: 0x0406, 0x0457: 0x0407, 0x0458: 0x0408, 0x0459: 0x0409, 0x045A: 0x040A, 0x045B: 0x040B, 0x045C: 0x040C, 0x045E: 0x040E, 0x045F: 0x040F, 0x0461: 0x0460, 0x0463: 0x0462, 0x0465: 0x0464, 0x0467: 0x0466, 0x0469: 0x0468, 0x046B: 0x046A, 0x046D: 0x046C, 0x046F: 0x046E, 0x0471: 0x0470, 0x0473: 0x0472, 0x0475: 0x0474, 0x0477: 0x0476, 0x0479: 0x0478, 0x047B: 0x047A, 0x047D: 0x047C, 0x047F: 0x047E, 0x0481: 0x0480, 0x0491: 0x0490, 0x0493: 0x0492, 0x0495: 0x0494, 0x0497: 0x0496, 0x0499: 0x0498, 0x049B: 0x049A, 0x049D: 0x049C ,0x049F: 0x049E, 0x04A1: 0x04A0, 0x04A3: 0x04A2, 0x04A5: 0x04A4, 0x04A7: 0x04A6, 0x04A9: 0x04A8, 0x04AB: 0x04AA, 0x04AD: 0x04AC, 0x04AF: 0x04AE, 0x04B1: 0x04B0, 0x04B3: 0x04B2, 0x04B5: 0x04B4, 0x04B7: 0x04B6, 0x04B9: 0x04B8, 0x04BB: 0x04BA, 0x04BD: 0x04BC, 0x04BF: 0x04BE, 0x04C2: 0x04C1, 0x04C4: 0x04C3, 0x04C8: 0x04C7, 0x04CC: 0x04CB, 0x04D1: 0x04D0, 0x04D3: 0x04D2, 0x04D5: 0x04D4, 0x04D7: 0x04D6, 0x04D9: 0x04D8, 0x04DB: 0x04DA, 0x04DD: 0x04DC, 0x04DF: 0x04DE, 0x04E1: 0x04E0, 0x04E3: 0x04E2, 0x04E5: 0x04E4, 0x04E7: 0x04E6, 0x04E9: 0x04E8, 0x04EB: 0x04EA, 0x04EF: 0x04EE, 0x04F1: 0x04F0, 0x04F3: 0x04F2, 0x04F5: 0x04F4, 0x04F9: 0x04F8, 0x0561: 0x0531, 0x0562: 0x0532, 0x0563: 0x0533, 0x0564: 0x0534, 0x0565: 0x0535, 0x0566: 0x0536, 0x0567: 0x0537, 0x0568: 0x0538, 0x0569: 0x0539, 0x056A: 0x053A, 0x056B: 0x053B, 0x056C: 0x053C, 0x056D: 0x053D, 0x056E: 0x053E, 0x056F: 0x053F, 0x0570: 0x0540, 0x0571: 0x0541, 0x0572: 0x0542, 0x0573: 0x0543, 0x0574: 0x0544, 0x0575: 0x0545, 0x0576: 0x0546, 0x0577: 0x0547, 0x0578: 0x0548, 0x0579: 0x0549, 0x057A: 0x054A, 0x057B: 0x054B, 0x057C: 0x054C, 0x057D: 0x054D, 0x057E: 0x054E, 0x057F: 0x054F, 0x0580: 0x0550, 0x0581: 0x0551, 0x0582: 0x0552, 0x0583: 0x0553, 0x0584: 0x0554, 0x0585: 0x0555, 0x0586: 0x0556, 0x10D0: 0x10A0, 0x10D1: 0x10A1, 0x10D2: 0x10A2, 0x10D3: 0x10A3, 0x10D4: 0x10A4, 0x10D5: 0x10A5, 0x10D6: 0x10A6, 0x10D7: 0x10A7, 0x10D8: 0x10A8, 0x10D9: 0x10A9, 0x10DA: 0x10AA, 0x10DB: 0x10AB, 0x10DC: 0x10AC, 0x10DD: 0x10AD, 0x10DE: 0x10AE, 0x10DF: 0x10AF, 0x10E0: 0x10B0, 0x10E1: 0x10B1, 0x10E2: 0x10B2, 0x10E3: 0x10B3, 0x10E4: 0x10B4, 0x10E5: 0x10B5, 0x10E6: 0x10B6, 0x10E7: 0x10B7, 0x10E8: 0x10B8, 0x10E9: 0x10B9, 0x10EA: 0x10BA, 0x10EB: 0x10BB, 0x10EC: 0x10BC, 0x10ED: 0x10BD, 0x10EE: 0x10BE, 0x10EF: 0x10BF, 0x10F0: 0x10C0, 0x10F1: 0x10C1, 0x10F2: 0x10C2, 0x10F3: 0x10C3, 0x10F4: 0x10C4, 0x10F5: 0x10C5, 0x1E01: 0x1E00, 0x1E03: 0x1E02, 0x1E05: 0x1E04, 0x1E07: 0x1E06, 0x1E09: 0x1E08, 0x1E0B: 0x1E0A, 0x1E0D: 0x1E0C, 0x1E0F: 0x1E0E, 0x1E11: 0x1E10, 0x1E13: 0x1E12, 0x1E15: 0x1E14, 0x1E17: 0x1E16, 0x1E19: 0x1E18, 0x1E1B: 0x1E1A, 0x1E1D: 0x1E1C, 0x1E1F: 0x1E1E, 0x1E21: 0x1E20, 0x1E23: 0x1E22, 0x1E25: 0x1E24, 0x1E27: 0x1E26, 0x1E29: 0x1E28, 0x1E2B: 0x1E2A, 0x1E2D: 0x1E2C, 0x1E2F: 0x1E2E, 0x1E31: 0x1E30, 0x1E33: 0x1E32, 0x1E35: 0x1E34, 0x1E37: 0x1E36, 0x1E39: 0x1E38, 0x1E3B: 0x1E3A, 0x1E3D: 0x1E3C, 0x1E3F: 0x1E3E, 0x1E41: 0x1E40, 0x1E43: 0x1E42, 0x1E45: 0x1E44, 0x1E47: 0x1E46, 0x1E49: 0x1E48, 0x1E4B: 0x1E4A, 0x1E4D: 0x1E4C, 0x1E4F: 0x1E4E, 0x1E51: 0x1E50, 0x1E53: 0x1E52, 0x1E55: 0x1E54, 0x1E57: 0x1E56, 0x1E59: 0x1E58, 0x1E5B: 0x1E5A, 0x1E5D: 0x1E5C, 0x1E5F: 0x1E5E, 0x1E61: 0x1E60, 0x1E63: 0x1E62, 0x1E65: 0x1E64, 0x1E67: 0x1E66, 0x1E69: 0x1E68, 0x1E6B: 0x1E6A, 0x1E6D: 0x1E6C, 0x1E6F: 0x1E6E, 0x1E71: 0x1E70, 0x1E73: 0x1E72, 0x1E75: 0x1E74, 0x1E77: 0x1E76, 0x1E79: 0x1E78, 0x1E7B: 0x1E7A, 0x1E7D: 0x1E7C, 0x1E7F: 0x1E7E, 0x1E81: 0x1E80, 0x1E83: 0x1E82, 0x1E85: 0x1E84, 0x1E87: 0x1E86, 0x1E89: 0x1E88, 0x1E8B: 0x1E8A, 0x1E8D: 0x1E8C, 0x1E8F: 0x1E8E, 0x1E91: 0x1E90, 0x1E93: 0x1E92, 0x1E95: 0x1E94, 0x1EA1: 0x1EA0, 0x1EA3: 0x1EA2, 0x1EA5: 0x1EA4, 0x1EA7: 0x1EA6, 0x1EA9: 0x1EA8, 0x1EAB: 0x1EAA, 0x1EAD: 0x1EAC, 0x1EAF: 0x1EAE, 0x1EB1: 0x1EB0, 0x1EB3: 0x1EB2, 0x1EB5: 0x1EB4, 0x1EB7: 0x1EB6, 0x1EB9: 0x1EB8, 0x1EBB: 0x1EBA, 0x1EBD: 0x1EBC, 0x1EBF: 0x1EBE, 0x1EC1: 0x1EC0, 0x1EC3: 0x1EC2, 0x1EC5: 0x1EC4, 0x1EC7: 0x1EC6, 0x1EC9: 0x1EC8, 0x1ECB: 0x1ECA, 0x1ECD: 0x1ECC, 0x1ECF: 0x1ECE, 0x1ED1: 0x1ED0, 0x1ED3: 0x1ED2, 0x1ED5: 0x1ED4, 0x1ED7: 0x1ED6, 0x1ED9: 0x1ED8, 0x1EDB: 0x1EDA, 0x1EDD: 0x1EDC, 0x1EDF: 0x1EDE, 0x1EE1: 0x1EE0, 0x1EE3: 0x1EE2, 0x1EE5: 0x1EE4, 0x1EE7: 0x1EE6, 0x1EE9: 0x1EE8, 0x1EEB: 0x1EEA, 0x1EED: 0x1EEC, 0x1EEF: 0x1EEE, 0x1EF1: 0x1EF0, 0x1EF3: 0x1EF2, 0x1EF5: 0x1EF4, 0x1EF7: 0x1EF6, 0x1EF9: 0x1EF8, 0x1F00: 0x1F08, 0x1F01: 0x1F09, 0x1F02: 0x1F0A, 0x1F03: 0x1F0B, 0x1F04: 0x1F0C, 0x1F05: 0x1F0D, 0x1F06: 0x1F0E, 0x1F07: 0x1F0F, 0x1F10: 0x1F18, 0x1F11: 0x1F19, 0x1F12: 0x1F1A, 0x1F13: 0x1F1B, 0x1F14: 0x1F1C, 0x1F15: 0x1F1D, 0x1F20: 0x1F28, 0x1F21: 0x1F29, 0x1F22: 0x1F2A, 0x1F23: 0x1F2B, 0x1F24: 0x1F2C, 0x1F25: 0x1F2D, 0x1F26: 0x1F2E, 0x1F27: 0x1F2F, 0x1F30: 0x1F38, 0x1F31: 0x1F39, 0x1F32: 0x1F3A, 0x1F33: 0x1F3B, 0x1F34: 0x1F3C, 0x1F35: 0x1F3D, 0x1F36: 0x1F3E, 0x1F37: 0x1F3F, 0x1F40: 0x1F48, 0x1F41: 0x1F49, 0x1F42: 0x1F4A, 0x1F43: 0x1F4B, 0x1F44: 0x1F4C, 0x1F45: 0x1F4D, 0x1F51: 0x1F59, 0x1F53: 0x1F5B, 0x1F55: 0x1F5D, 0x1F57: 0x1F5F, 0x1F60: 0x1F68, 0x1F61: 0x1F69, 0x1F62: 0x1F6A, 0x1F63: 0x1F6B, 0x1F64: 0x1F6C, 0x1F65: 0x1F6D, 0x1F66: 0x1F6E, 0x1F67: 0x1F6F, 0x1F80: 0x1F88, 0x1F81: 0x1F89, 0x1F82: 0x1F8A, 0x1F83: 0x1F8B, 0x1F84: 0x1F8C, 0x1F85: 0x1F8D, 0x1F86: 0x1F8E, 0x1F87: 0x1F8F, 0x1F90: 0x1F98, 0x1F91: 0x1F99, 0x1F92: 0x1F9A, 0x1F93: 0x1F9B, 0x1F94: 0x1F9C, 0x1F95: 0x1F9D, 0x1F96: 0x1F9E, 0x1F97: 0x1F9F, 0x1FA0: 0x1FA8, 0x1FA1: 0x1FA9, 0x1FA2: 0x1FAA, 0x1FA3: 0x1FAB, 0x1FA4: 0x1FAC, 0x1FA5: 0x1FAD, 0x1FA6: 0x1FAE, 0x1FA7: 0x1FAF, 0x1FB0: 0x1FB8, 0x1FB1: 0x1FB9, 0x1FD0: 0x1FD8, 0x1FD1: 0x1FD9, 0x1FE0: 0x1FE8, 0x1FE1: 0x1FE9, 0x24D0: 0x24B6, 0x24D1: 0x24B7, 0x24D2: 0x24B8, 0x24D3: 0x24B9, 0x24D4: 0x24BA, 0x24D5: 0x24BB, 0x24D6: 0x24BC, 0x24D7: 0x24BD, 0x24D8: 0x24BE, 0x24D9: 0x24BF, 0x24DA: 0x24C0, 0x24DB: 0x24C1, 0x24DC: 0x24C2, 0x24DD: 0x24C3, 0x24DE: 0x24C4, 0x24DF: 0x24C5, 0x24E0: 0x24C6, 0x24E1: 0x24C7, 0x24E2: 0x24C8, 0x24E3: 0x24C9, 0x24E4: 0x24CA, 0x24E5: 0x24CB, 0x24E6: 0x24CC, 0x24E7: 0x24CD, 0x24E8: 0x24CE, 0x24E9: 0x24CF, 0xFF41: 0xFF21, 0xFF42: 0xFF22, 0xFF43: 0xFF23, 0xFF44: 0xFF24, 0xFF45: 0xFF25, 0xFF46: 0xFF26, 0xFF47: 0xFF27, 0xFF48: 0xFF28, 0xFF49: 0xFF29, 0xFF4A: 0xFF2A, 0xFF4B: 0xFF2B, 0xFF4C: 0xFF2C, 0xFF4D: 0xFF2D, 0xFF4E: 0xFF2E, 0xFF4F: 0xFF2F, 0xFF50: 0xFF30, 0xFF51: 0xFF31, 0xFF52: 0xFF32, 0xFF53: 0xFF33, 0xFF54: 0xFF34, 0xFF55: 0xFF35, 0xFF56: 0xFF36, 0xFF57: 0xFF37, 0xFF58: 0xFF38, 0xFF59: 0xFF39, 0xFF5A: 0xFF3A}\nlet x = \"rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr\nrrrrrrrrrrrrrrrrrrrr\"\nproc translateStr*(toTrans: string, transTable: Table[int, int]): string =\n    for charac in toTrans.runes:\n        if charac.int in transTable:\n            result &= transTable[charac.int].Rune\n        else:\n            result &= charac\n\necho translateStr(x, lowTOUP.toTable)\n"
  },
  {
    "path": "test/regression/2999-buggy-wrapping/rc",
    "content": "add-highlighter global/ wrap -indent -word\n"
  },
  {
    "path": "test/regression/2999-buggy-wrapping/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"l\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"et x = \\\"rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"rrrrrrrrrrrrrrrrrrrr\\\"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"proc translateStr*(toTrans: string, transTable: Table[int, int]): string =\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"    for charac in toTrans.runes:\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"        if charac.int in transTable:\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"            result &= transTable[charac.int].Rune\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"        else:\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"            result &= charac\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"echo translateStr(x, lowTOUP.toTable)\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 2:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/3010-extra_word_chars-fails-with-other-buffers/cmd",
    "content": ":e other<ret>iword░<esc><ret><c-o>A<c-x>W<c-n>\n"
  },
  {
    "path": "test/regression/3010-extra_word_chars-fails-with-other-buffers/in",
    "content": "wo\n"
  },
  {
    "path": "test/regression/3010-extra_word_chars-fails-with-other-buffers/out",
    "content": "word░\n"
  },
  {
    "path": "test/regression/3010-extra_word_chars-fails-with-other-buffers/rc",
    "content": "set global extra_word_chars ░\n"
  },
  {
    "path": "test/regression/3025-last-buffer-does-not-work-in-draft-context/cmd",
    "content": "<a-P>\n"
  },
  {
    "path": "test/regression/3025-last-buffer-does-not-work-in-draft-context/out",
    "content": "*test*out\n"
  },
  {
    "path": "test/regression/3025-last-buffer-does-not-work-in-draft-context/rc",
    "content": "declare-option str-list bufnames\neval -draft -save-regs '' %{\n    edit -scratch *test*\n    set-option global bufnames %val{bufname}\n    db!\n    set-option -add global bufnames %val{bufname}\n    set-register '\"' %opt{bufnames}\n}\n"
  },
  {
    "path": "test/regression/3041-invalid-regex-highlight-accross-regions/cmd",
    "content": ""
  },
  {
    "path": "test/regression/3041-invalid-regex-highlight-accross-regions/in",
    "content": "- _foo\n- bar_\n- _baz_\n\n_foo\nbar_\n"
  },
  {
    "path": "test/regression/3041-invalid-regex-highlight-accross-regions/rc",
    "content": "add-highlighter shared/test regions\nadd-highlighter shared/test/base default-region regions\nadd-highlighter shared/test/base/base default-region regex _[^_]+_ 0:+b\nadd-highlighter shared/test/item region '^- \\K' $ ref test/base\nadd-highlighter window/ ref test\n"
  },
  {
    "path": "test/regression/3041-invalid-regex-highlight-accross-regions/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"-\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" _foo\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"- bar_\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"- \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"bold\"] }, \"contents\": \"_baz_\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"bold\"] }, \"contents\": \"_foo\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"bold\"] }, \"contents\": \"bar_\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/3048-word-wrapping-broken/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/3048-word-wrapping-broken/in",
    "content": "[ab\n"
  },
  {
    "path": "test/regression/3048-word-wrapping-broken/rc",
    "content": "add-highlighter global/ wrap -word -width 2\n"
  },
  {
    "path": "test/regression/3048-word-wrapping-broken/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ab\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/3219-scroll-json-ui/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/3219-scroll-json-ui/in",
    "content": "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n"
  },
  {
    "path": "test/regression/3219-scroll-json-ui/script",
    "content": "ui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"0\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"02\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"03\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"04\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"05\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"06\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"07\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"08\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"09\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"scroll\", \"params\": [ 2, 0, 0] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"03\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"04\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"05\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"06\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"07\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"08\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"09\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/3270-crash-in-autocomplete/cmd",
    "content": "cvery<c-x><c-w><c-n>()<c-o>\n"
  },
  {
    "path": "test/regression/3270-crash-in-autocomplete/in",
    "content": "test(bool very_long_variable_name) : very_long_variable_name(%(replace))\n"
  },
  {
    "path": "test/regression/3270-crash-in-autocomplete/out",
    "content": "test(bool very_long_variable_name) :\nvery_long_variable_name(very_long_variable_name())\n"
  },
  {
    "path": "test/regression/3270-crash-in-autocomplete/rc",
    "content": "source \"%val{runtime}/rc/tools/autowrap.kak\"\nautowrap-enable\n"
  },
  {
    "path": "test/regression/3275-crash-on-replace-at-buffer-start/cmd",
    "content": "R\n"
  },
  {
    "path": "test/regression/3275-crash-on-replace-at-buffer-start/in",
    "content": "foo\n"
  },
  {
    "path": "test/regression/3275-crash-on-replace-at-buffer-start/out",
    "content": "oo\n"
  },
  {
    "path": "test/regression/3349-crash-in-completion/cmd",
    "content": "gei<c-n><backspace><backspace>\n"
  },
  {
    "path": "test/regression/3349-crash-in-completion/in",
    "content": "first\na\n"
  },
  {
    "path": "test/regression/3349-crash-in-completion/out",
    "content": "first\n"
  },
  {
    "path": "test/regression/3349-crash-in-completion/rc",
    "content": "set-option global autocomplete insert\ndeclare-option -hidden completions my_completions\nset-option global completers option=my_completions\nset-option global my_completions \"2.1+1@%val(timestamp)\" \"a||a\"\n"
  },
  {
    "path": "test/regression/3349-crash-in-completion/script",
    "content": "ui_out -ignore 4\nsleep .2 # trigger insert completion auto update\n"
  },
  {
    "path": "test/regression/3388-command-line-parsing-does-not-preserve-invalid-utf8/cmd",
    "content": "\"aR\n"
  },
  {
    "path": "test/regression/3388-command-line-parsing-does-not-preserve-invalid-utf8/out",
    "content": "= ⌘\n"
  },
  {
    "path": "test/regression/3388-command-line-parsing-does-not-preserve-invalid-utf8/rc",
    "content": "evaluate-commands %sh{ printf 'set-register a \"\\275\\262\\75\\274\\040\\342\\214\\230\"' }\n"
  },
  {
    "path": "test/regression/3398-readonly-fifo-failure/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/3398-readonly-fifo-failure/out",
    "content": "blah\n"
  },
  {
    "path": "test/regression/3398-readonly-fifo-failure/rc",
    "content": "nop %sh{ mkfifo test-fifo; ( printf 'blah' > test-fifo & ) </dev/null >/dev/null 2>&1 }\nedit -readonly -fifo test-fifo out\n"
  },
  {
    "path": "test/regression/3398-readonly-fifo-failure/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[scratch][fifo][readonly]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"lah\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/3439-parse-ascii-newline-as-return/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/3439-parse-ascii-newline-as-return/out",
    "content": "1\n    2\n    3\n\n"
  },
  {
    "path": "test/regression/3439-parse-ascii-newline-as-return/rc",
    "content": "exec O %sh{ printf '1\\n    2\\n    3' } <esc>\n"
  },
  {
    "path": "test/regression/3472-crash-on-to-sentence-start/cmd",
    "content": "j[s\n"
  },
  {
    "path": "test/regression/3472-crash-on-to-sentence-start/in",
    "content": "a\n\n"
  },
  {
    "path": "test/regression/3472-crash-on-to-sentence-start/kak_selection_desc",
    "content": "2.1,1.1\n"
  },
  {
    "path": "test/regression/3478-crash-on-scroll/cmd",
    "content": "%sfoo<ret>(<pagedown>\n"
  },
  {
    "path": "test/regression/3478-crash-on-scroll/in",
    "content": "foo\nfoo\n"
  },
  {
    "path": "test/regression/3478-crash-on-scroll/out",
    "content": "foo\nfoo\n"
  },
  {
    "path": "test/regression/3489-crash-on-paragraph-begin/cmd",
    "content": "j<a-[>p\n"
  },
  {
    "path": "test/regression/3489-crash-on-paragraph-begin/in",
    "content": "\n\n"
  },
  {
    "path": "test/regression/3489-crash-on-paragraph-begin/kak_selection_desc",
    "content": "2.1,1.1\n"
  },
  {
    "path": "test/regression/3495-crash-highlighting-hidden-lines/cmd",
    "content": ""
  },
  {
    "path": "test/regression/3495-crash-highlighting-hidden-lines/in",
    "content": "{}{<>}\n"
  },
  {
    "path": "test/regression/3495-crash-highlighting-hidden-lines/rc",
    "content": "add-highlighter global/rs regions\nadd-highlighter global/rs/r1 region \\{ \\} ref sub\n\nadd-highlighter shared/sub regions\nadd-highlighter shared/sub/r2 region  < > group\nadd-highlighter shared/sub/r2/ regex X 0:red\n\ndeclare-option range-specs replaced_part 1 1.2,1.4|Placeholder\nadd-highlighter global/ replace-ranges replaced_part\n"
  },
  {
    "path": "test/regression/3544-capture-get-lost/cmd",
    "content": "/(a)<ret>xcx<c-r>1x<esc>\n"
  },
  {
    "path": "test/regression/3544-capture-get-lost/in",
    "content": "xax\n"
  },
  {
    "path": "test/regression/3544-capture-get-lost/out",
    "content": "xax\n"
  },
  {
    "path": "test/regression/3669-pipe-adds-extra-newline/cmd",
    "content": "|cksum<ret>\n"
  },
  {
    "path": "test/regression/3669-pipe-adds-extra-newline/in",
    "content": "%(foo)\n"
  },
  {
    "path": "test/regression/3669-pipe-adds-extra-newline/out",
    "content": "2470157969 3\n"
  },
  {
    "path": "test/regression/3733-modeline-parsing-off-by-one-line/cmd",
    "content": ":modeline-parse<ret>\n%\n:exec c %opt{tabstop}<ret>\n"
  },
  {
    "path": "test/regression/3733-modeline-parsing-off-by-one-line/in",
    "content": "# kak:tabstop=1:\n# kak:tabstop=2:\n# kak:tabstop=3:\nfoo\nfoo\nfoo\nfoo\n"
  },
  {
    "path": "test/regression/3733-modeline-parsing-off-by-one-line/out",
    "content": "1\n"
  },
  {
    "path": "test/regression/3733-modeline-parsing-off-by-one-line/rc",
    "content": "source \"%val{runtime}/rc/detection/modeline.kak\"\nset-option global modelines 1\n"
  },
  {
    "path": "test/regression/3735-modeline-arbitrary-code-execution/cmd",
    "content": ":modeline-parse<ret>\n%\n:exec c %opt{pwned}<ret>\n"
  },
  {
    "path": "test/regression/3735-modeline-arbitrary-code-execution/in",
    "content": "# kak: tabstop=2;set-option\tbuffer\tpwned\tyes\n"
  },
  {
    "path": "test/regression/3735-modeline-arbitrary-code-execution/out",
    "content": "false\n"
  },
  {
    "path": "test/regression/3735-modeline-arbitrary-code-execution/rc",
    "content": "source \"%val{runtime}/rc/detection/modeline.kak\"\ndeclare-option -hidden bool pwned no\nset-option global tabstop 42\n"
  },
  {
    "path": "test/regression/3757-crash-on-capture-group-select/cmd",
    "content": "%<a-s>1sfoo(.*)|<ret>\n"
  },
  {
    "path": "test/regression/3757-crash-on-capture-group-select/in",
    "content": "foo\nbar\n"
  },
  {
    "path": "test/regression/3757-crash-on-capture-group-select/out",
    "content": "foo\nbar\n"
  },
  {
    "path": "test/regression/3799-incorrect-region-match/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/3799-incorrect-region-match/in",
    "content": "print << \"\";\npart of heredoc\n\nnot part of heredoc\n"
  },
  {
    "path": "test/regression/3799-incorrect-region-match/rc",
    "content": "add-highlighter global/regions regions\nadd-highlighter global/regions/heredoc region -match-capture <<\\h*\"(\\w*)\" ^(\\w*)$ fill red\n"
  },
  {
    "path": "test/regression/3799-incorrect-region-match/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"p\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"rint \" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"<< \\\"\\\";\\u000a\" }], [{ \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"part of heredoc\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"not part of heredoc\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/3909-crash-on-closing-buffer-with-user-mapping/cmd",
    "content": "i<a-;><space>d<esc>\n"
  },
  {
    "path": "test/regression/3909-crash-on-closing-buffer-with-user-mapping/rc",
    "content": "edit -scratch\nmap global user d :delete-buffer<ret>\n"
  },
  {
    "path": "test/regression/4052-replace-range-vs-whitespace-highlighter-interaction/cmd",
    "content": ""
  },
  {
    "path": "test/regression/4052-replace-range-vs-whitespace-highlighter-interaction/in",
    "content": "fn main() {\n    let my_str = String::from(\"abc\");\n}\n"
  },
  {
    "path": "test/regression/4052-replace-range-vs-whitespace-highlighter-interaction/rc",
    "content": "declare-option -hidden range-specs test_inlay_hints\nadd-highlighter window/whitespaces show-whitespaces -tab '›' -tabpad '⋅' -spc ' ' -nbsp '⍽'\nadd-highlighter window/test_inlay_hints replace-ranges test_inlay_hints\nset buffer test_inlay_hints %val{timestamp} '2.15+0|{Information}{\\}: String'\n"
  },
  {
    "path": "test/regression/4052-replace-range-vs-whitespace-highlighter-interaction/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"f\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"n\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"main()\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"{\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"    \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"let\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"my_str\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \": String\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"=\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"String::from(\\\"abc\\\");\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"}\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/4086-ruby-deindenting-other-lines-than-keywords/cmd",
    "content": "c<space><space>end\n"
  },
  {
    "path": "test/regression/4086-ruby-deindenting-other-lines-than-keywords/in",
    "content": "class MyClass\n  def method\n%( )\n\n  expression\nend\n"
  },
  {
    "path": "test/regression/4086-ruby-deindenting-other-lines-than-keywords/out",
    "content": "class MyClass\n  def method\n  end\n\n  expression\nend\n"
  },
  {
    "path": "test/regression/4086-ruby-deindenting-other-lines-than-keywords/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/ruby.kak\"\nset buffer filetype ruby\n"
  },
  {
    "path": "test/regression/4378-line-explicit-completion-buggy/cmd",
    "content": "a<c-x>l<c-n><esc>\n"
  },
  {
    "path": "test/regression/4378-line-explicit-completion-buggy/in",
    "content": "a b\na%( )\n"
  },
  {
    "path": "test/regression/4378-line-explicit-completion-buggy/out",
    "content": "a b\na b\n"
  },
  {
    "path": "test/regression/4414-crash-on-paste-all/cmd",
    "content": "<a-p>\n"
  },
  {
    "path": "test/regression/4414-crash-on-paste-all/error",
    "content": "'exec': nothing to paste\n"
  },
  {
    "path": "test/regression/4460-nul-byte-in-regex/cmd",
    "content": "*%s<ret>d\n"
  },
  {
    "path": "test/regression/4460-nul-byte-in-regex/out",
    "content": "\n"
  },
  {
    "path": "test/regression/4471-crash-on-alt-t-with-one-char-file/cmd",
    "content": "<a-t><ret>\n"
  },
  {
    "path": "test/regression/4471-crash-on-alt-t-with-one-char-file/error",
    "content": "'exec': no selections remaining\n"
  },
  {
    "path": "test/regression/4476-invalid-line-join/cmd",
    "content": "%<a-s><a-S><a-j>\n"
  },
  {
    "path": "test/regression/4476-invalid-line-join/in",
    "content": "ab\nac\n"
  },
  {
    "path": "test/regression/4476-invalid-line-join/out",
    "content": "ab ac\n"
  },
  {
    "path": "test/regression/4519-regex-alternation-priority/cmd",
    "content": "%sfoobaz|foo|foobar<ret>\n"
  },
  {
    "path": "test/regression/4519-regex-alternation-priority/in",
    "content": "foobar\n"
  },
  {
    "path": "test/regression/4519-regex-alternation-priority/kak_selections",
    "content": "foo\n"
  },
  {
    "path": "test/regression/4521-alt-mapping-broken-in-lower-case-modes/cmd",
    "content": "g<a-X>d\n"
  },
  {
    "path": "test/regression/4521-alt-mapping-broken-in-lower-case-modes/in",
    "content": "foo\n"
  },
  {
    "path": "test/regression/4521-alt-mapping-broken-in-lower-case-modes/out",
    "content": "fo\n"
  },
  {
    "path": "test/regression/4521-alt-mapping-broken-in-lower-case-modes/rc",
    "content": "map global goto <a-X> l\n"
  },
  {
    "path": "test/regression/4601-int-min-arg/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/4601-int-min-arg/out",
    "content": "ok\n"
  },
  {
    "path": "test/regression/4601-int-min-arg/rc",
    "content": "try %{\n    nop %arg{-2147483648}\n} catch %{\n    exec iok<esc>\n}\n"
  },
  {
    "path": "test/regression/4605-fifo-hang/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/4605-fifo-hang/rc",
    "content": "exec 5000oabcdefghi<esc>%yppppp\nnop %sh{\n    echo \"write $kak_response_fifo\" > \"$kak_command_fifo\"\n    cat \"$kak_response_fifo\"\n}\n"
  },
  {
    "path": "test/regression/4659-scroll-issue-with-replace-ranges/cmd",
    "content": "6l\n"
  },
  {
    "path": "test/regression/4659-scroll-issue-with-replace-ranges/in",
    "content": "hello world\n"
  },
  {
    "path": "test/regression/4659-scroll-issue-with-replace-ranges/rc",
    "content": "decl range-specs range\naddhl window/ replace-ranges range\nset buffer range %val{timestamp} 1.6+0|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
  },
  {
    "path": "test/regression/4659-scroll-issue-with-replace-ranges/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"w\" }]], { \"line\": 0, \"column\": 79 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/4669-eol-highlight-to-column-highlighter/cmd",
    "content": "J\n"
  },
  {
    "path": "test/regression/4669-eol-highlight-to-column-highlighter/in",
    "content": "foo\nbar\n"
  },
  {
    "path": "test/regression/4669-eol-highlight-to-column-highlighter/rc",
    "content": "add-highlighter window/ column 6 ,red\n"
  },
  {
    "path": "test/regression/4669-eol-highlight-to-column-highlighter/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }], [{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ar\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }]], { \"line\": 1, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/4674-show-whitespaces-horizontal-scroll-assert/cmd",
    "content": "gl\n"
  },
  {
    "path": "test/regression/4674-show-whitespaces-horizontal-scroll-assert/in",
    "content": "\t\t\t\t\t\t\t\t\t\tfoobar\n"
  },
  {
    "path": "test/regression/4674-show-whitespaces-horizontal-scroll-assert/rc",
    "content": "add-highlighter window/ show-whitespaces\nadd-highlighter window/ number-lines\n"
  },
  {
    "path": "test/regression/4674-show-whitespaces-horizontal-scroll-assert/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"       →       →       →       →       →       →       →       →       \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"fooba\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"r\" }]], { \"line\": 0, \"column\": 79 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\n"
  },
  {
    "path": "test/regression/4750-crash-on-append-empty-output-at-end-of-buffer/cmd",
    "content": "<a-!>true<ret>\n"
  },
  {
    "path": "test/regression/4753-assert-in-display-line-split/cmd",
    "content": "ypp\n"
  },
  {
    "path": "test/regression/4753-assert-in-display-line-split/in",
    "content": "🔎\n"
  },
  {
    "path": "test/regression/4753-assert-in-display-line-split/rc",
    "content": "add-highlighter global/ column 5 +r\n"
  },
  {
    "path": "test/regression/4779-crash-when-pasting-with-multiple-overlapping-selections/cmd",
    "content": "x_y10+p\n"
  },
  {
    "path": "test/regression/4779-crash-when-pasting-with-multiple-overlapping-selections/in",
    "content": "../\n"
  },
  {
    "path": "test/regression/4779-crash-when-pasting-with-multiple-overlapping-selections/out",
    "content": "../../../../../../../../../../../\n"
  },
  {
    "path": "test/regression/4839-scroll-invalid-cursor/cmd",
    "content": "l<pagedown>\n"
  },
  {
    "path": "test/regression/4839-scroll-invalid-cursor/in",
    "content": "ab\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n😃\n"
  },
  {
    "path": "test/regression/4839-scroll-invalid-cursor/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"😃\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/4843-trim-front-split-glyph/cmd",
    "content": "lvl\n"
  },
  {
    "path": "test/regression/4843-trim-front-split-glyph/enabled",
    "content": "#!/bin/sh\n[ $(echo -n \"⌛\" | wc -m) = '1' ] && locale | grep LC_CTYPE | grep -qi 'utf-*8'\n"
  },
  {
    "path": "test/regression/4843-trim-front-split-glyph/in",
    "content": "abc\n⌛c\n⌛d\n⌛e\n"
  },
  {
    "path": "test/regression/4843-trim-front-split-glyph/rc",
    "content": "add-highlighter window/ regex d 0:green\nadd-highlighter window/ regex ^[^\\n]+e 0:yellow\n"
  },
  {
    "path": "test/regression/4843-trim-front-split-glyph/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"b\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"c\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"c\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"green\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"d\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"e\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/4844-crash-on-empty-paste/cmd",
    "content": "<a-o>Cpp\n"
  },
  {
    "path": "test/regression/4844-crash-on-empty-paste/out",
    "content": "\n\n"
  },
  {
    "path": "test/regression/4859-regex-invalid-behaviour/cmd",
    "content": "ge<a-/><ret>d\n"
  },
  {
    "path": "test/regression/4859-regex-invalid-behaviour/in",
    "content": "             ddddd\n             aaaaa\n             bbbbb\n"
  },
  {
    "path": "test/regression/4859-regex-invalid-behaviour/out",
    "content": "            bbbbb\n"
  },
  {
    "path": "test/regression/4859-regex-invalid-behaviour/rc",
    "content": "reg slash '(?S)((^ {13}d.*\\n)(^ {13}.*\\n)*?)?^.'\n"
  },
  {
    "path": "test/regression/4887-torn-utf8-sequence/cmd",
    "content": ":exec %{<percent>cç<lt>esc>x_<semicolon>iab<lt>esc>}<ret>\n"
  },
  {
    "path": "test/regression/4887-torn-utf8-sequence/out",
    "content": "abç\n"
  },
  {
    "path": "test/regression/4896-remap-executing-mapping/cmd",
    "content": "!printf $kak_opt_source_count<ret>l\n<space>s\n!printf $kak_opt_source_count<ret>\n"
  },
  {
    "path": "test/regression/4896-remap-executing-mapping/out",
    "content": "12\n"
  },
  {
    "path": "test/regression/4896-remap-executing-mapping/rc",
    "content": "map global user s %exp{:source %%{%val{source}}<ret>} -docstring \"re-source my kakrc\"\ndeclare-option int source_count\nset-option -add global source_count 1\n"
  },
  {
    "path": "test/regression/4896-unmap-executing-mapping/cmd",
    "content": "<space>u\n"
  },
  {
    "path": "test/regression/4896-unmap-executing-mapping/out",
    "content": "123456\n"
  },
  {
    "path": "test/regression/4896-unmap-executing-mapping/rc",
    "content": "map global user u %{i123<esc>:unmap global user u<ret>i456<esc>}\n"
  },
  {
    "path": "test/regression/4926-crash-with-fold-and-ranges/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/4926-crash-with-fold-and-ranges/in",
    "content": "{[foo]}\n{[bar]}\n{[baz]}\n%( )\n"
  },
  {
    "path": "test/regression/4926-crash-with-fold-and-ranges/rc",
    "content": "declare-option range-specs fold %val{timestamp} 1.1,3.8|folded\n\nadd-highlighter buffer/regions regions\nadd-highlighter buffer/regions/curly region \\{ \\} regions\nadd-highlighter buffer/regions/curly/brace region \\[ \\] fill green\nadd-highlighter buffer/fold replace-ranges fold\n"
  },
  {
    "path": "test/regression/4927-crash-jumping-to-eol/cmd",
    "content": "<a-l>\n"
  },
  {
    "path": "test/regression/4927-crash-jumping-to-eol/in",
    "content": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n\n\n"
  },
  {
    "path": "test/regression/4927-crash-jumping-to-eol/rc",
    "content": "add-highlighter global/ column 60 red\nadd-highlighter buffer/regions regions\nadd-highlighter buffer/regions/ default-region fill green\n"
  },
  {
    "path": "test/regression/4959-bad-default-region/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/4959-bad-default-region/out",
    "content": "bad-highlighter\n"
  },
  {
    "path": "test/regression/4959-bad-default-region/rc",
    "content": "add-highlighter shared/test regions\ntry %{\n    add-highlighter shared/test/ default-region invalid highlighter\n} catch %{\n    exec ibad-highlighter<esc>\n}\n"
  },
  {
    "path": "test/regression/5001-line-highlighting-breaks-at-region/cmd",
    "content": ""
  },
  {
    "path": "test/regression/5001-line-highlighting-breaks-at-region/in",
    "content": "#foo\nbar\n"
  },
  {
    "path": "test/regression/5001-line-highlighting-breaks-at-region/rc",
    "content": "add-highlighter window/test regions\nadd-highlighter window/test/macro region %{#} %{(?=\\\\n)} group\nadd-highlighter window/test/code default-region group\nadd-highlighter window/ number-lines\nface global LineNumbers default,default+F\nface global LineNumberCursor default,default+rF\nadd-highlighter window/ line 2 ,red\n"
  },
  {
    "path": "test/regression/5001-line-highlighting-breaks-at-region/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\",\"final_attr\"] }, \"contents\": \" 1│\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"#\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_bg\",\"final_attr\"] }, \"contents\": \" 2│\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"red\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"                                                                         \" }]], { \"line\": 0, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 3] }'\n"
  },
  {
    "path": "test/regression/5076-empty-text-should-not-change-prompt-history/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/5076-empty-text-should-not-change-prompt-history/in",
    "content": "abab\n"
  },
  {
    "path": "test/regression/5076-empty-text-should-not-change-prompt-history/kak_selections",
    "content": "a a\n"
  },
  {
    "path": "test/regression/5076-empty-text-should-not-change-prompt-history/script",
    "content": "ui_out -ignore 7\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"xsa<ret>\" ] }'\nui_out -ignore 6\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"xs<ret>\" ] }'\nui_out -ignore 5\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"xs<ret>\" ] }'\nui_out -ignore 5\n"
  },
  {
    "path": "test/regression/5118-crash-on-scroll-with-insert-completion-and-wrap/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/5118-crash-on-scroll-with-insert-completion-and-wrap/in",
    "content": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n%(29)\n"
  },
  {
    "path": "test/regression/5118-crash-on-scroll-with-insert-completion-and-wrap/rc",
    "content": "add-highlighter window/ wrap\n"
  },
  {
    "path": "test/regression/5118-crash-on-scroll-with-insert-completion-and-wrap/script",
    "content": "ui_out -ignore 6\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"a<space>2<c-x><c-w>\" ] }'\nui_out -until-grep menu_show\nui_out -until-grep refresh\nui_out -until-grep menu_show\nui_out -until-grep refresh\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"scroll\", \"params\": [ -5, 0, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"3\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"6\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"7\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"8\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"9\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/5120-double-free-or-corruption-crash/cmd",
    "content": "ggQm*nWd<a-l>d<a-d>n<a-c>\\fo<c-n>{<c-r>\"}<esc>Q8q\n"
  },
  {
    "path": "test/regression/5120-double-free-or-corruption-crash/in",
    "content": "Lorem ipsum dolor sit amet consectetur adipisicing elit[1]. Maxime mollitia,\nmolestiae quas vel sint commodi[2] repudiandae consequuntur voluptatum laborum\nnumquam blanditiis harum quisquam eius sed odit fugiat iusto fuga praesentium\noptio, eaque rerum![3] Provident similique accusantium nemo autem. Veritatis\nobcaecati tenetur iure eius earum ut molestias architecto voluptate aliquam\nnihil,[4] eveniet aliquid culpa officia aut! Impedit sit sunt quaerat, odit,\ntenetur error, harum nesciunt ipsum debitis quas aliquid. Reprehenderit,\nquia. Quo neque error repudiandae fuga? Ipsa laudantium molestias eos \nsapiente officiis modi at sunt excepturi expedita sint? Sed quibusdam\nrecusandae alias error harum maxime adipisci amet laborum[5]. Perspiciatis \nminima nesciunt dolorem! Officiis iure rerum voluptates a cumque velit \nquibusdam sed amet tempora[6]. Sit laborum ab, eius fugit doloribus tenetur \nfugiat, temporibus enim commodi iusto libero magni deleniti quod quam \nconsequuntur! Commodi minima excepturi repudiandae velit hic maxime\ndoloremque[7]. Quaerat provident commodi consectetur veniam similique ad \nearum omnis ipsum saepe, voluptas, hic voluptates pariatur est explicabo \nfugiat, dolorum eligendi quam cupiditate excepturi mollitia maiores labore \nsuscipit quas? Nulla, placeat[8]. Voluptatem quaerat non architecto ab laudantium\nmodi minima sunt esse temporibus sint culpa, recusandae aliquam numquam \ntotam ratione voluptas quod exercitationem fuga[9]. Possimus quis earum veniam \nquasi aliquam eligendi, placeat qui corporis!\n\n\n[1] This is the first footnote.\n\n[2] This is the second footnote.\n\n[3] This is the third footnote.\n\n[4] This is the fourth footnote.\n\n[5] This is the fifth footnote.\n\n[6] This is the sixth footnote.\n\n[7] This is the seventh footnote.\n\n[8] This is the eighth footnote.\n\n[9] This is the Ninth footnote.\n"
  },
  {
    "path": "test/regression/5122-dot-fails-after-macro-replay/cmd",
    "content": "QA?<esc>Qq.\n"
  },
  {
    "path": "test/regression/5122-dot-fails-after-macro-replay/out",
    "content": "???\n"
  },
  {
    "path": "test/regression/5124-assert-in-redo/cmd",
    "content": "%du<a-@>U\n"
  },
  {
    "path": "test/regression/5124-assert-in-redo/error",
    "content": "'exec': nothing left to redo\n"
  },
  {
    "path": "test/regression/5124-assert-in-redo/in",
    "content": "foo     bar\n"
  },
  {
    "path": "test/regression/5253-line-and-column-highlighter-issue/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/5253-line-and-column-highlighter-issue/in",
    "content": "\nTEST\n\n"
  },
  {
    "path": "test/regression/5253-line-and-column-highlighter-issue/rc",
    "content": "add-highlighter global/ column 40 \"default,green\"\nadd-highlighter global/ line 2 \"default,blue\"\n"
  },
  {
    "path": "test/regression/5253-line-and-column-highlighter-issue/script",
    "content": "ui_out -until-grep '\"method\": \"draw\"' '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"                                      \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"green\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"TEST\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"                                                                           \" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"                                      \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"green\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/5298-missing-capture-register/cmd",
    "content": "s(.*)<ret>\n"
  },
  {
    "path": "test/regression/5298-missing-capture-register/in",
    "content": "%(foo)\n"
  },
  {
    "path": "test/regression/5298-missing-capture-register/kak_reg_0",
    "content": "foo\n"
  },
  {
    "path": "test/regression/5316-crash-on-duplicate-cursor-replace-at-end/cmd",
    "content": "<a-O>+rc\n"
  },
  {
    "path": "test/regression/5316-crash-on-duplicate-cursor-replace-at-end/out",
    "content": "\nc\n"
  },
  {
    "path": "test/regression/5324-BufSetOption-triggers-twice/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/5324-BufSetOption-triggers-twice/out",
    "content": "hook-ran\n"
  },
  {
    "path": "test/regression/5324-BufSetOption-triggers-twice/rc",
    "content": "hook global BufCreate .*/foo %{ set buffer filetype foo }\nhook global BufSetOption filetype=foo %{ exec -buffer out ihook-ran<esc> }\nedit foo\ndelete-buffer foo\n"
  },
  {
    "path": "test/regression/5338-crash-when-changing-buffer-on-WinDiplay/cmd",
    "content": ":e -scratch<ret><c-o>\n"
  },
  {
    "path": "test/regression/5338-crash-when-changing-buffer-on-WinDiplay/rc",
    "content": "hook global WinDisplay .* %{ edit out }\n"
  },
  {
    "path": "test/regression/5364-exec-and-view-commands-anomalous-interaction/cmd",
    "content": "<c-l>:exec<space>ggi<lt>ret<gt><lt>esc<gt>/^c$<lt>ret<gt>vt<ret>\n"
  },
  {
    "path": "test/regression/5364-exec-and-view-commands-anomalous-interaction/in",
    "content": "a\nb\nc\nd\ne\n"
  },
  {
    "path": "test/regression/5364-exec-and-view-commands-anomalous-interaction/script",
    "content": "ui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"c\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"d\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"e\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/5383-missing-redraw-with-Gtvv/cmd",
    "content": "ge\n"
  },
  {
    "path": "test/regression/5383-missing-redraw-with-Gtvv/in",
    "content": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n"
  },
  {
    "path": "test/regression/5383-missing-redraw-with-Gtvv/rc",
    "content": "map global normal = Gtvv\n"
  },
  {
    "path": "test/regression/5383-missing-redraw-with-Gtvv/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"7\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"8\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"9\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"25\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"26\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"27\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"28\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"29\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"30\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 23, \"column\": 2 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out -until-grep 'refresh'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out -until-grep 'refresh'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [\"=\"]}'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"3\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5\\u000a\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"6\\u000a\" }], [{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"7\" }, { \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"8\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"9\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }]], { \"line\": 6, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [\"=\"]}'\nui_out -until-grep 'refresh'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"2\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"3\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"4\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"5\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"6\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"7\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"8\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"9\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"10\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"11\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"13\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"14\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"15\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"16\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"17\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"18\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"19\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"20\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"21\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"22\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23\\u000a\" }], [{ \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"24\\u000a\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/5405-error-face-on-empty-prompt/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/5405-error-face-on-empty-prompt/script",
    "content": "ui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":\" ] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[{ \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \":\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }], 0, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"prompt\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"a<backspace>\" ] }'\nui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[{ \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \":\" }], [{ \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"cyan\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }], 0, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:1 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"prompt\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out -ignore 2\n# Error face redraw was happening here\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [false] }'\n"
  },
  {
    "path": "test/regression/5409-scrolloff-incorrectly-offset-column-highlighter/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/5409-scrolloff-incorrectly-offset-column-highlighter/in",
    "content": "12%(3)456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789\n"
  },
  {
    "path": "test/regression/5409-scrolloff-incorrectly-offset-column-highlighter/rc",
    "content": "add-highlighter global/ column 71 \"default,white\"\nset-option global scrolloff 4,4\n"
  },
  {
    "path": "test/regression/5409-scrolloff-incorrectly-offset-column-highlighter/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"12\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"3\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"456789 123456789 123456789 123456789 123456789 123456789 123456789 \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"23456789\\u000a\" }]], { \"line\": 0, \"column\": 2 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/5414-a-p-on-multibyte-character/cmd",
    "content": "y<a-p>;yp\n"
  },
  {
    "path": "test/regression/5414-a-p-on-multibyte-character/in",
    "content": "%(foo ü)\n"
  },
  {
    "path": "test/regression/5414-a-p-on-multibyte-character/out",
    "content": "foo üfoo üü\n"
  },
  {
    "path": "test/regression/5415-final-fg-attributes-lost-to-final-attr/cmd",
    "content": "\n"
  },
  {
    "path": "test/regression/5415-final-fg-attributes-lost-to-final-attr/in",
    "content": "a <b c> d\n\n"
  },
  {
    "path": "test/regression/5415-final-fg-attributes-lost-to-final-attr/rc",
    "content": "face global Whitespace yellow+f\naddhl global/ show-whitespaces -spc .\nface global before_underline cyan\nface global add_underline ,,magenta+c\nface global remove_underline +a\naddhl global/a regex <([^>]*)> 1:before_underline\naddhl global/b regex [a-z] 0:add_underline\naddhl global/c regex <([^>]*)> 1:remove_underline\naddhl global/d regex <([^>]*)> 1:before_underline\n"
  },
  {
    "path": "test/regression/5415-final-fg-attributes-lost-to-final-attr/script",
    "content": "ui_out -ignore 1\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"magenta\", \"attributes\": [\"curly_underline\"] }, \"contents\": \"a\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \".\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"<\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"magenta\", \"attributes\": [\"final_attr\"] }, \"contents\": \"b\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\",\"final_attr\"] }, \"contents\": \".\" }, { \"face\": { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"magenta\", \"attributes\": [\"final_attr\"] }, \"contents\": \"c\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \">\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \".\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"magenta\", \"attributes\": [\"curly_underline\"] }, \"contents\": \"d\" }, { \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }], [{ \"face\": { \"fg\": \"yellow\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [\"final_fg\"] }, \"contents\": \"¬\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\n"
  },
  {
    "path": "test/regression/5439-visual-vertical-movement-breaks-on-out-of-screen-target/cmd",
    "content": "<c-l>gu\n"
  },
  {
    "path": "test/regression/5439-visual-vertical-movement-breaks-on-out-of-screen-target/in",
    "content": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b\n\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa %(b)\n"
  },
  {
    "path": "test/regression/5439-visual-vertical-movement-breaks-on-out-of-screen-target/kak_selection_desc",
    "content": "2.1,2.1\n"
  },
  {
    "path": "test/regression/595-smart-search-unicode/cmd",
    "content": "*n\n"
  },
  {
    "path": "test/regression/595-smart-search-unicode/in",
    "content": "д é д … é …\n"
  },
  {
    "path": "test/regression/595-smart-search-unicode/kak_selections_desc",
    "content": "1.7,1.7\n"
  },
  {
    "path": "test/regression/612-assertion-with-NormalBegin-hook/cmd",
    "content": ":q<ret>\n"
  },
  {
    "path": "test/regression/612-assertion-with-NormalBegin-hook/rc",
    "content": "hook global ModeChange push:.*:normal %{ face StatusLine default }\n"
  },
  {
    "path": "test/regression/633-spurious-new-line-inserted-when-replacing-at-end/cmd",
    "content": "x~\n"
  },
  {
    "path": "test/regression/633-spurious-new-line-inserted-when-replacing-at-end/in",
    "content": "blah\n"
  },
  {
    "path": "test/regression/633-spurious-new-line-inserted-when-replacing-at-end/out",
    "content": "BLAH\n"
  },
  {
    "path": "test/regression/638-highlight-codepoint-with-bracket/cmd",
    "content": "w\n"
  },
  {
    "path": "test/regression/638-highlight-codepoint-with-bracket/in",
    "content": "“We ought to scrape this planet clean of every living thing on it,”\n"
  },
  {
    "path": "test/regression/638-highlight-codepoint-with-bracket/out",
    "content": "“We ought to scrape this planet clean of every living thing on it,”\n"
  },
  {
    "path": "test/regression/638-highlight-codepoint-with-bracket/rc",
    "content": "add-highlighter window/ regex '[“”]' 0:red\n"
  },
  {
    "path": "test/regression/638-highlight-codepoint-with-bracket/script",
    "content": "ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw\", \"params\": [[[{ \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"“\" }, { \"face\": { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"We\" }, { \"face\": { \"fg\": \"black\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"ought to scrape this planet clean of every living thing on it,\" }, { \"face\": { \"fg\": \"red\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"”\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\\u000a\" }]], { \"line\": 0, \"column\": 3 }, { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, 0] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"info_hide\", \"params\": [] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"out 1:4 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\nui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\n"
  },
  {
    "path": "test/regression/643-crash-on-a-@-on-specially-crafted-buffer/cmd",
    "content": "gg2l<a-@>\n"
  },
  {
    "path": "test/regression/643-crash-on-a-@-on-specially-crafted-buffer/in",
    "content": "    hello\n"
  },
  {
    "path": "test/regression/643-crash-on-a-@-on-specially-crafted-buffer/out",
    "content": "    hello\n"
  },
  {
    "path": "test/regression/654-crash-on-undo-after-macro/cmd",
    "content": "Qxy<a-p>;dQ3qu\n"
  },
  {
    "path": "test/regression/654-crash-on-undo-after-macro/in",
    "content": "aaa\n"
  },
  {
    "path": "test/regression/654-crash-on-undo-after-macro/out",
    "content": "aaa\n"
  },
  {
    "path": "test/regression/699-to-eol-from-eol/cmd",
    "content": "x;<a-l>\n"
  },
  {
    "path": "test/regression/699-to-eol-from-eol/in",
    "content": "my line\n"
  },
  {
    "path": "test/regression/699-to-eol-from-eol/kak_selections_desc",
    "content": "1.8,1.8\n"
  },
  {
    "path": "test/regression/704-crash-when-using-a-hook-on-NormalBegin-and-NormalEnd/cmd",
    "content": ""
  },
  {
    "path": "test/regression/704-crash-when-using-a-hook-on-NormalBegin-and-NormalEnd/rc",
    "content": "hook global ModeChange push:.*:normal 'exec s'\nhook global ModeChange pop:normal:.* 'exec s'\n"
  },
  {
    "path": "test/regression/733-selection-list-from-string-not-valid/cmd",
    "content": ":select 100.100,100.200<ret>\n"
  },
  {
    "path": "test/regression/733-selection-list-from-string-not-valid/in",
    "content": "foo\n"
  },
  {
    "path": "test/regression/733-selection-list-from-string-not-valid/out",
    "content": "foo\n"
  },
  {
    "path": "test/regression/743-crash-on-replace-undo/cmd",
    "content": "%s1<ret>xyjRu\n"
  },
  {
    "path": "test/regression/743-crash-on-replace-undo/in",
    "content": "line 1\nline 2\n\nline 1\nline 2\n"
  },
  {
    "path": "test/regression/743-crash-on-replace-undo/out",
    "content": "line 1\nline 2\n\nline 1\nline 2\n"
  },
  {
    "path": "test/regression/751-wrong-selection-after-undo/cmd",
    "content": "mS\\d+<ret>du\n"
  },
  {
    "path": "test/regression/751-wrong-selection-after-undo/in",
    "content": "{\n11, 22\n};\n"
  },
  {
    "path": "test/regression/751-wrong-selection-after-undo/kak_quoted_selections",
    "content": "'{\n' ', ' '\n}'\n"
  },
  {
    "path": "test/regression/787-crash-after-S/cmd",
    "content": "S.<ret>\n"
  },
  {
    "path": "test/regression/787-crash-after-S/error",
    "content": "'exec': nothing selected\n"
  },
  {
    "path": "test/regression/809-alt-f-t-to-first-char/cmd",
    "content": "<a-f>f\n"
  },
  {
    "path": "test/regression/809-alt-f-t-to-first-char/in",
    "content": "fooba%(r)\n"
  },
  {
    "path": "test/regression/809-alt-f-t-to-first-char/kak_quoted_selections",
    "content": "'foobar'\n"
  },
  {
    "path": "test/regression/811-double-width-codepoints/cmd",
    "content": "j\n"
  },
  {
    "path": "test/regression/811-double-width-codepoints/enabled",
    "content": "#!/bin/sh\nlocale | grep LC_CTYPE | grep -qi 'utf-*8'\n"
  },
  {
    "path": "test/regression/811-double-width-codepoints/in",
    "content": "1234%(5)67%(8)9012\n一二三四五六\n"
  },
  {
    "path": "test/regression/811-double-width-codepoints/kak_quoted_selections",
    "content": "'三' '四'\n"
  },
  {
    "path": "test/regression/844-prev-word-at-buffer-start/cmd",
    "content": "bb\n"
  },
  {
    "path": "test/regression/844-prev-word-at-buffer-start/in",
    "content": "  %(word)\n"
  },
  {
    "path": "test/regression/844-prev-word-at-buffer-start/kak_quoted_selections",
    "content": "'  '\n"
  },
  {
    "path": "test/regression/860-python-incorrect-commenting/cmd",
    "content": "<a-j>welc<ret><esc>\n"
  },
  {
    "path": "test/regression/860-python-incorrect-commenting/in",
    "content": "# comment\nif haha\n"
  },
  {
    "path": "test/regression/860-python-incorrect-commenting/out",
    "content": "# comment\n# if haha\n"
  },
  {
    "path": "test/regression/860-python-incorrect-commenting/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/python.kak\"\nset buffer filetype python\n"
  },
  {
    "path": "test/regression/872-indentation-misbeahviour-c++/cmd",
    "content": "i<ret><esc>\n"
  },
  {
    "path": "test/regression/872-indentation-misbeahviour-c++/in",
    "content": "class foo { \n    %(p)rivate: \n        int a; \n        int b; \n    public: \n        foo() {} \n        void bar() { \n        } \n}; \n"
  },
  {
    "path": "test/regression/872-indentation-misbeahviour-c++/out",
    "content": "class foo { \n\n    private: \n        int a; \n        int b; \n    public: \n        foo() {} \n        void bar() { \n        } \n}; \n"
  },
  {
    "path": "test/regression/872-indentation-misbeahviour-c++/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/regression/918-wrong-asterisk-inserted/cmd",
    "content": "A<ret><esc>\n"
  },
  {
    "path": "test/regression/918-wrong-asterisk-inserted/in",
    "content": "void foo(int\n         *ba%(r)\n"
  },
  {
    "path": "test/regression/918-wrong-asterisk-inserted/out",
    "content": "void foo(int\n         *bar\n\n"
  },
  {
    "path": "test/regression/918-wrong-asterisk-inserted/rc",
    "content": "source \"%val{runtime}/colors/default.kak\"\nsource \"%val{runtime}/rc/filetype/c-family.kak\"\nset buffer filetype cpp\n"
  },
  {
    "path": "test/regression/921-keep-empty-line-matches-all-lines/cmd",
    "content": "%<a-s><a-k>^$<ret>\n"
  },
  {
    "path": "test/regression/921-keep-empty-line-matches-all-lines/in",
    "content": "non-empty\nnon-empty\n\n"
  },
  {
    "path": "test/regression/921-keep-empty-line-matches-all-lines/kak_quoted_selections",
    "content": "'\n'\n"
  },
  {
    "path": "test/regression/993-user-text-object/cmd",
    "content": "<a-a>:,,<ret>\n"
  },
  {
    "path": "test/regression/993-user-text-object/error",
    "content": "'exec': no selections remaining\n"
  },
  {
    "path": "test/regression/quoted-vals/cmd",
    "content": ":exec \"%%c%val{selections_desc}<lt>esc>\"<ret>\n"
  },
  {
    "path": "test/regression/quoted-vals/in",
    "content": "%(sel1)\n %(sel2)\n"
  },
  {
    "path": "test/regression/quoted-vals/out",
    "content": "2.2,2.5 1.1,1.4\n"
  },
  {
    "path": "test/run",
    "content": "#!/bin/sh\n\n# Color codes ├──────────────────────────────────────────────────────────────────\nnone='\\033[0m'; red='\\033[31m'; green='\\033[32m'; yellow='\\033[33m'; magenta='\\033[35m'; bold='\\033[1m'\n\n# Main ├────────────────────────────────────────────────────────────────────────\n\nmain() {\n  kak_commands='\n        set global autoreload yes\n        set global autoinfo \"\"\n        set global autocomplete \"\"\n        try %{\n            exec -save-regs / %{%s%\\(\\K[^)]+\\)<ret>a<backspace><esc>i<backspace><backspace><c-u><esc><a-;>}\n        } catch %{ exec gg }\n        try %{\n            eval -no-hooks -draft %{ edit -existing rc; delete-buffer }\n            try %{ source rc } catch %{ echo -to-file stderr -end-of-line %val{error}; kill! 1 }\n        }\n        try %{ eval -no-hooks -draft %{ edit cmd; exec \\%H\"zy; delete-buffer } }\n        hook global RuntimeError \"\\d+:\\d+: (.+)\" %{ echo -to-file stderr -end-of-line %val{hook_param_capture_1}; kill! 1 }\n        exec -with-maps -with-hooks %reg{z}\n        exec -with-hooks <c-l>\n      '\n\n  root=\"$PWD\"\n  testdir=\"$(dirname $0)\"\n  kak=\"$(realpath $testdir)/../src/kak\"\n  tmpdir=\"${TMPDIR:-/tmp}\"\n  work=$(mktemp -d $tmpdir/kak-tests.XXXXXXXX)\n  session=\"kak-tests\"\n  if [ -n \"$XDG_RUNTIME_DIR\" ]; then\n    session_path=\"${XDG_RUNTIME_DIR}/kakoune/$session\"\n  else\n    session_path=\"${TMPDIR:-/tmp}/kakoune/${USER}/$session\"\n  fi\n  trap \"rm -Rf $work\" EXIT\n\n  number_tests=0\n  number_failures=0\n  for dir in $(find \"${@:-$testdir}\" -type d | sed 's|^\\./||' | sort); do\n    cd $root/$dir;\n    if [ ! -f cmd ]; then\n      echo \"$dir\"\n      continue\n    elif [ -x enabled ] && ! ./enabled >/dev/null 2>&1; then\n      printf \"${yellow}$dir (disabled)${none}\\n\"\n      continue\n    fi\n\n    env_vars=$(ls -1 kak_* 2>/dev/null)\n    number_tests=$(($number_tests + 1))\n    mkdir -p $work/$dir\n    cp in cmd rc env $work/$dir/ 2>/dev/null\n    cd $work/$dir;\n\n    mkfifo ui-in ui-out\n    touch in; cp in out\n    rm -f \"$session_path\"\n    (\n      if [ -f env ]; then\n        . ./env\n      fi\n      exec $DEBUGGER $kak out -n -s \"$session\" -ui json -e \"$kak_commands\" >ui-out <ui-in\n    ) &\n    kakpid=$!\n\n    failed=0\n    exec 4<ui-out 3>ui-in\n\n    if [ -f \"${root}/${dir}/script\" ]; then\n      . \"${root}/${dir}/script\"\n    else\n      # At least wait for kak to initialize so we don't deadlock\n      ui_out '{ \"jsonrpc\": \"2.0\", \"method\": \"set_ui_options\", \"params\": [{}] }'\n    fi\n\n    finished_commands |$kak -p \"$session\" 2>/dev/null\n    cat <&4 >/dev/null\n\n    wait $kakpid\n    retval=$?\n\n    exec 3>&- 4<&-\n\n    if [ ! -e $root/$dir/error ]; then # failure not expected\n      if [ $retval -ne 0 ]; then\n        printf \"${red}%s${none}\\n\" \"$dir\"\n        echo \"  Kakoune returned error $retval\"\n        [ -f stderr ] && printf \"  ${yellow}%s${none}\\n\" \"$(cat stderr)\"\n        failed=1\n      else\n        for file in out $env_vars; do\n          if [ -f $root/$dir/$file ] && ! cmp -s $root/$dir/$file $file; then\n            fail_ifn\n            show_diff $root/$dir/$file $file\n          fi\n        done\n        if [ $failed -ne 0 ] && [ -f debug ]; then\n          printf \"\\n${yellow}debug buffer:${none}\\n\"\n          cat debug\n        fi\n      fi\n    else # failure expected\n      if [ -f stderr ]; then\n        sed -i -e 's/^[0-9]*:[0-9]*: //g' stderr\n        if [ -s $root/$dir/error ] && ! cmp -s $root/$dir/error stderr; then\n          printf \"${yellow}%s${none}\\n\" \"$dir\"\n          show_diff $root/$dir/error stderr\n          failed=1\n        fi\n      elif [ $retval -eq 0 ]; then\n        printf \"${red}%s${none}\\n\" \"$dir\"\n        echo \"  Expected failure, but Kakoune returned 0\"\n        failed=1\n      fi\n    fi\n\n    if [ $failed -eq 0 ]; then\n      printf \"${green}%s${none}\\n\" \"$dir\"\n    else\n      number_failures=$(($number_failures + 1))\n    fi\n  done\n\n  if [ $number_failures -gt 0 ]; then\n    color=$red\n  else\n    color=$green\n  fi\n  printf \"\\n${color}Summary: %s tests, %s failures${none}\\n\" $number_tests $number_failures\n  exit $number_failures\n}\n\n# Utility ├─────────────────────────────────────────────────────────────────────\n\nfail_ifn() {\n  if [ $failed -eq 0 ]; then\n    printf \"${red}%s${none}\\n\" \"$dir\"\n    failed=1\n  fi\n}\n\nassert_eq() {\n  if [ ! \"$1\" = \"$2\" ]; then\n    fail_ifn\n    if command -v git > /dev/null; then\n        echo \"$1\" > expected\n        echo \"$2\" > actual\n        git --no-pager diff --color-words --no-index expected actual\n    else\n        printf \"  ${red}- %s\\n  ${green}+ %s${none}\\n\" \"$1\" \"$2\"\n    fi\n  fi\n}\n\nshow_diff() {\n  diff -u $1 $2 | while IFS='' read -r line; do\n    first_character=$(printf '%s\\n' \"$line\" | cut -b 1)\n    case $first_character in\n      +) color=$green ;;\n      -) color=$red ;;\n      @) color=$magenta ;;\n      *) color=$none ;;\n    esac\n    printf \"${color}%s${none}\\n\" \"$line\"\n  done\n}\n\nfinished_commands() {\n  printf %s 'eval -client client0 %{\n               eval -buffer *debug* write -force debug\n            '\n  for env_var in $env_vars; do\n      case $env_var in\n          kak_quoted_*) printf 'echo -to-file %s -end-of-line -quoting shell -- %s\\n' \"$env_var\" \"%val{${env_var##kak_quoted_}}\" ;;\n          kak_*) printf 'echo -to-file %s -end-of-line -- %s\\n' \"$env_var\" \"%val{${env_var##kak_}}\" ;;\n      esac\n  done\n  printf %s '\n               write -force out\n               quit!\n             }\n            '\n}\n\n# Script Assertions ├───────────────────────────────────────────────────────────\n\nui_in() {\n  printf '%s\\n' \"$1\" >&3\n}\n\nui_out() {\n  arg=$1\n  shift\n  case \"$arg\" in\n  -ignore)\n    skip_count=$1\n    shift\n    while [ $skip_count -gt 0 ]; do\n      read -r event <&4\n      skip_count=$(( skip_count - 1 ))\n    done\n    ;;\n  -until)\n    expected=$1\n    shift\n    while read -r event <&4; do\n      [ \"$event\" = \"$expected\" ] && break\n    done\n    ;;\n  -until-grep)\n    pattern=$1\n    shift\n    while read -r event <&4; do\n      if printf %s \"$event\" | grep \"$pattern\" >/dev/null; then\n        if [ $# -ne 0 ]; then\n            assert_eq \"$1\" \"$event\"\n            shift\n        fi\n        break\n      fi\n    done\n    ;;\n  *)\n    read -r event <&4\n    assert_eq \"$arg\" \"$event\"\n    ;;\n  esac\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "test/shell/list-syntax/cmd",
    "content": "\n"
  },
  {
    "path": "test/shell/list-syntax/out",
    "content": "foo\nbar\n'foo'bar'\n\n"
  },
  {
    "path": "test/shell/list-syntax/rc",
    "content": "declare-option str-list my_list 'foo' 'bar' '''foo''bar'''\nevaluate-commands %sh{\n    eval set -- $kak_quoted_opt_my_list\n    for elem; do\n        echo exec \"'i$(printf %s \"$elem\" | sed -e s/\\'/\\'\\'/g)<ret><esc>'\"\n    done\n}\n"
  },
  {
    "path": "test/shell/prompt-shell-script-candidates/cmd",
    "content": "\n"
  },
  {
    "path": "test/shell/prompt-shell-script-candidates/out",
    "content": "bar\n"
  },
  {
    "path": "test/shell/prompt-shell-script-candidates/rc",
    "content": "set-option global autocomplete prompt\ndefine-command foo %{\n    prompt -shell-script-candidates %{ printf 'foo\\nbar\\nhaz\\n' } ': ' %{exec i %val{text} <esc>}\n}\n"
  },
  {
    "path": "test/shell/prompt-shell-script-candidates/script",
    "content": "ui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":foo<ret>b\" ] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [false] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_show\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"prompt\"] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"<tab><ret>\" ] }'\n"
  },
  {
    "path": "test/shell/prompt-shell-script-completion/cmd",
    "content": "\n"
  },
  {
    "path": "test/shell/prompt-shell-script-completion/out",
    "content": "foo\n"
  },
  {
    "path": "test/shell/prompt-shell-script-completion/rc",
    "content": "set-option global autocomplete prompt\ndefine-command foo %{\n    prompt -shell-script-completion %{ printf 'foo\\nbar\\nbaz\\n' } ': ' %{exec i %val{text} <esc>}\n}\n"
  },
  {
    "path": "test/shell/prompt-shell-script-completion/script",
    "content": "ui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [true] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \":foo<ret>b\" ] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"refresh\", \"params\": [false] }'\nui_out -until '{ \"jsonrpc\": \"2.0\", \"method\": \"menu_show\", \"params\": [[[{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"foo\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"bar\" }], [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"baz\" }]], { \"line\": 0, \"column\": 0 }, { \"fg\": \"white\", \"bg\": \"blue\", \"underline\": \"default\", \"attributes\": [] }, { \"fg\": \"blue\", \"bg\": \"white\", \"underline\": \"default\", \"attributes\": [] }, \"prompt\"] }'\nui_in '{ \"jsonrpc\": \"2.0\", \"method\": \"keys\", \"params\": [ \"<tab><ret>\" ] }'\n"
  },
  {
    "path": "test/shell/quoted-range/cmd",
    "content": "!printf '%s' \"$kak_quoted_opt_my_ranges\"<ret>\n"
  },
  {
    "path": "test/shell/quoted-range/out",
    "content": "'1' '1.1,1.1|a b' '1.2,1.2|b c'\n"
  },
  {
    "path": "test/shell/quoted-range/rc",
    "content": "declare-option range-specs my_ranges 1 '1.1,1.1|a b' '1.2,1.2|b c'\n"
  },
  {
    "path": "test/tools/git/blame-in-diff/cmd",
    "content": ":run<ret>\n"
  },
  {
    "path": "test/tools/git/blame-in-diff/enabled",
    "content": "#!/bin/sh\ncommand -v git >/dev/null && command -v perl >/dev/null\n"
  },
  {
    "path": "test/tools/git/blame-in-diff/in",
    "content": "line 1\nline 2\nline 3\n"
  },
  {
    "path": "test/tools/git/blame-in-diff/kak_selection",
    "content": "line 2\n\n"
  },
  {
    "path": "test/tools/git/blame-in-diff/rc",
    "content": "declare-option str jumpclient\nsource \"%val{runtime}/rc/filetype/diff.kak\"\nsource \"%val{runtime}/rc/tools/git.kak\"\n\ndefine-command run %{\n    try %{\n        git init\n        git add\n        git commit --message 'initial commit'\n        execute-keys %{2gIchanged <esc>}\n        write\n        git commit --all --message 'changed line 2'\n        # Show the commit, jumping to the new version of line 2.\n        git blame-jump\n        hook -once buffer BufCloseFifo .* %{\n            evaluate-commands -client client0 %{\n                # We've jumped to the new version of line 2. Move to the old\n                # version so we can annotate the old version of the file.\n                execute-keys k\n                git blame\n                hook -once buffer BufCloseFifo .* %{\n                    execute-keys -client client0 x\n                    # Wait for a redraw before reporting completion.\n                    hook -once buffer NormalIdle .* %{\n                        echo -to-file fifo\n                    }\n                }\n            }\n        }\n    } catch %{\n        echo -to-file fifo\n        kill! 1\n    }\n}\n"
  },
  {
    "path": "test/tools/git/blame-in-diff/script",
    "content": "cat <&4 >/dev/null &\nmkfifo fifo 2>/dev/null\ncat fifo\n# We should have jumped to the old version of line 2, assert on kak_selection.\n"
  },
  {
    "path": "test/tools/git/blame-jump-message/cmd",
    "content": ":run<ret>\n"
  },
  {
    "path": "test/tools/git/blame-jump-message/enabled",
    "content": "#!/bin/sh\ncommand -v git >/dev/null && command -v perl >/dev/null\n"
  },
  {
    "path": "test/tools/git/blame-jump-message/in",
    "content": "\n"
  },
  {
    "path": "test/tools/git/blame-jump-message/rc",
    "content": "declare-option str jumpclient\nsource \"%val{runtime}/rc/filetype/diff.kak\"\nsource \"%val{runtime}/rc/tools/git.kak\"\n\ndefine-command run %[\n    git init\n    git add\n    git commit --message \"Don't break on single quotes or unbalanced {\"\n    git blame-jump\n]\n"
  },
  {
    "path": "test/tools/git/blame-jump-message/script",
    "content": "expected_subject=$(cat <<'EOF'\n2017-07-13 A U Thor \"Don't break on single quotes or unbalanced {\"\nEOF\n)\nexpected_subject_json=\\\"$(printf '%s' \"$expected_subject\" | sed 's/\"/\\\\\"/g')\\\"\nexpected_draw_status='{ \"jsonrpc\": \"2.0\", \"method\": \"draw_status\", \"params\": [[], [{ \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": '\"$expected_subject_json\"' }], -1, [{ \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"*git* 13:2 \" }, { \"face\": { \"fg\": \"black\", \"bg\": \"yellow\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"[scratch]\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" \" }, { \"face\": { \"fg\": \"blue\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \"1 sel\" }, { \"face\": { \"fg\": \"default\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }, \"contents\": \" - client0@[kak-tests]\" }], { \"fg\": \"cyan\", \"bg\": \"default\", \"underline\": \"default\", \"attributes\": [] }] }'\n\nui_out -until-grep 'draw_status.*single quotes' \"$expected_draw_status\"\nui_out -ignore 2\n"
  },
  {
    "path": "test/tools/git/env",
    "content": "for git_var in $(set | awk -F= '/^GIT_/ {print $1}')\ndo\n    unset -v \"$git_var\"\ndone\nexport GIT_CONFIG_NOSYSTEM=true\nexport GIT_CONFIG_GLOBAL=/dev/null\nexport GIT_AUTHOR_NAME=\"A U Thor\"\nexport GIT_AUTHOR_EMAIL=\"author@domain.tld\"\nexport GIT_COMMITTER_NAME=\"C O Mitter\"\nexport GIT_COMMITTER_EMAIL=\"committer@domain.tld\"\nexport GIT_AUTHOR_DATE=\"1500000000 -0500\"\nexport GIT_COMMITTER_DATE=\"1500000000 -0500\"\nexport GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main # Suppress noisy warning.\n"
  },
  {
    "path": "test/tools/patch/ignore-first-file/cmd",
    "content": "%:patch tee applied.diff<ret><ret>!echo Applied:; cat applied.diff; echo; echo Updated buffer:<ret>\n"
  },
  {
    "path": "test/tools/patch/ignore-first-file/enabled",
    "content": "#!/bin/sh\ncommand -v perl >/dev/null\n"
  },
  {
    "path": "test/tools/patch/ignore-first-file/in",
    "content": "diff -ur a/file1 b/file1\n--- a/file1\n+++ b/file1\n@@ -1 +1 @@\n file1 here\ndiff -ur a/file2 b/file2\n--- a/file2\n+++ b/file2\n@@ -1 +1 @@\n-file2 here\n+modified file2 here\n"
  },
  {
    "path": "test/tools/patch/ignore-first-file/out",
    "content": "Applied:\ndiff -ur a/file2 b/file2\n--- a/file2\n+++ b/file2\n@@ -1 +1 @@\n-file2 here\n+modified file2 here\n\nUpdated buffer:\ndiff -ur a/file1 b/file1\n--- a/file1\n+++ b/file1\n@@ -1 +1 @@\n file1 here\ndiff -ur a/file2 b/file2\n--- a/file2\n+++ b/file2\n@@ -1 +1 @@\n modified file2 here\n"
  },
  {
    "path": "test/tools/patch/ignore-first-file/rc",
    "content": "source \"%val{runtime}/rc/tools/patch.kak\"\n"
  },
  {
    "path": "test/tools/patch/signature/cmd",
    "content": "%:patch tee applied.diff<ret><ret>!echo Applied:; cat applied.diff; echo; echo Updated buffer:<ret>\n"
  },
  {
    "path": "test/tools/patch/signature/enabled",
    "content": "#!/bin/sh\ncommand -v perl >/dev/null\n"
  },
  {
    "path": "test/tools/patch/signature/in",
    "content": "diff -ur a/file1 b/file1\n--- a/file1\n+++ b/file1\n@@ -1,3 +1,3 @@\n context\n-file1 here\n+modified file1 here\n context\n-- \n2.43.0\n"
  },
  {
    "path": "test/tools/patch/signature/out",
    "content": "Applied:\ndiff -ur a/file1 b/file1\n--- a/file1\n+++ b/file1\n@@ -1,3 +1,3 @@\n context\n-file1 here\n+modified file1 here\n context\n\nUpdated buffer:\ndiff -ur a/file1 b/file1\n--- a/file1\n+++ b/file1\n@@ -1,3 +1,3 @@\n context\n modified file1 here\n context\n-- \n2.43.0\n"
  },
  {
    "path": "test/tools/patch/signature/rc",
    "content": "source \"%val{runtime}/rc/tools/patch.kak\"\n"
  }
]