ACME Text Editor: LSP Tool Support

Overview

Acme editor with acme-lsp/ccls editing C++

Acme editor with acme-lsp/ccls editing C++

Acme is an excellent editor. If you have to edit a complex C++ code base, using acme-lsp with ccls results in a big productivity boost. In this post I describe my Acme setup on Darwin with a focus on acme-lsp and ccls.

ACME LSP

To install acme-lsp and acmefocused please follow these instructions:

GO111MODULE=on go get github.com/fhs/acme-lsp/cmd/acme-lsp@latest
GO111MODULE=on go get github.com/fhs/acme-lsp/cmd/L@latest
GO111MODULE=on go get github.com/fhs/acme-lsp/cmd/acmefocused@latest

Follow the Hints and Tips outlined at the acme-lsp page to scripts like Ldef, Lrefs, Ltype, etc., so that you can easily execute those commands with a single middle click.

ACMEFOCUSED with SKHD

While one can create scripts to be executed via a middle click, I prefer to use keyboard short-cuts for common tasks like auto completion, show definitions/references, etc. via skhd.

skhd is a hotkey daemon for macOS that focuses on responsiveness and performance. Hotkeys are defined in a text file through a simple DSL. skhd is able to hotload its configuration file, meaning that hotkeys can be edited and updated live while skhd is running.

; tail -n12 .config/skhd/skhdrc
# acme-lsp: complete
shift + cmd - f [
  "acme" : L comp -e | 9p write acme/`{dial unix!`{namespace}/acmefocused}/errors
]
# acme-lsp: refs
shift + cmd - r [
  "acme" : L refs | 9p write acme/`{dial unix!`{namespace}/acmefocused}/errors
]
# acme-lsp: defs
shift + cmd - d [
  "acme" : L def | 9p write acme/`{dial unix!`{namespace}/acmefocused}/errors
]

Start ACME LSP via Launchd

Using launchd to fire up acme-lsp is what I decided is best for my use case.

Create a 9lab.org.acme-lsp.plist file with the following contents:

; touch $HOME/Library/LaunchAgents/9lab.org.acme-lsp.plist
; E $HOME/Library/LaunchAgents/9lab.org.acme-lsp.plist

Replace $HOME with the path to your home folder:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>9lab.org.acme-lsp</string>
  <key>ProgramArguments</key>
  <array>
    <string>$HOME/bin/acme-lsp</string>
  </array>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PLAN9</key>
    <string>/usr/local/plan9</string>
    <key>PATH</key>
    <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/plan9/bin:$HOME/bin</string>
  </dict>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/usr/local/var/log/9lab/acme-lsp.out.log</string>
  <key>StandardErrorPath</key>
  <string>/usr/local/var/log/9lab/acme-lsp.err.log</string>
</dict>
</plist>

Start ACME Focused via Launchd

Create a 9lab.org.acmefocused.plist file with the following contents:

; touch $HOME/Library/LaunchAgents/9lab.org.acmefocused.plist
; E $HOME/Library/LaunchAgents/9lab.org.acmefocused.plist

Replace $HOME with the path to your home folder:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <keyacmefocused>Label</key>
  <string>9lab.org.acmefocused</string>
  <key>ProgramArguments</key>
  <array>
    <string>$HOME/bin/acmefocused</string>
  </array>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PLAN9</key>
    <string>/usr/local/plan9</string>
    <key>PATH</key>
    <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/plan9/bin</string>
  </dict>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/usr/local/var/log/9lab/acmefocused.out.log</string>
  <key>StandardErrorPath</key>
  <string>/usr/local/var/log/9lab/acmefocused.err.log</string>
</dict>
</plist>

Launchd Helper Script

The following rc helper script eases restart of acme-lsp and acmefocused :

; E $HOME/bin/lsp
; cat $HOME/bin/lsp
#!/usr/local/plan9/bin/rc

fn Help {
  echo `{basename $0}^' [list|stop|start|restart|logclear]'
}

fn List {
  launchctl list | grep '9lab.org.acme'
}

fn PropertyLists {
  launchctl dumpstate | grep 'path = .*9lab.org.acme.*.plist$' | ssam 's/^.* = //g'
}

fn Restart {
  for(p in `{PropertyLists}) {
    launchctl unload -w $p
    launchctl load -w $p
  }
}

fn Stop {
  for(p in `{PropertyLists}) {
    launchctl unload -w $p
  }
}

fn Start {
  for(p in `{PropertyLists}) {
    launchctl load -w $p
  }
}

fn LogClear {
  for(p in `{PropertyLists}) {
    # Extract log files from .plist launch agent definition:
    #
    #  <key>StandardOutPath</key>
    #  <string>/usr/local/var/log/9lab/acmefocused.out.log</string>
    #  <key>StandardErrorPath</key>
    #  <string>/usr/local/var/log/9lab/acmefocused.err.log</string>
    #
    logs=`{cat $p | grep '\.log' | awk -F '[<|>]' '{print $3}'}
    for(l in $logs) {
      echo 'Truncating log file ">'^$l^'"'
      >$l
    }
  }
}
# -----------------------------------------------------------------
switch ($#*) {
  case 0
    Help
  case *
    switch ($1) {
      case list
        List
      case start
        Start
      case stop
        Stop
      case restart
        Restart
      case logclear
        LogClear
      case *
        Help
    }
}

Now one can easily restart and list the status of acmefocused and acme-lsp:

; lsp restart
$HOME/Library/LaunchAgents/9lab.org.acmefocused.plist: Operation now in progress
$HOME/Library/LaunchAgents/9lab.org.acme-lsp.plist: Operation now in progress
; lsp list
3850	0	9lab.org.acme-lsp
3847	0	9lab.org.acmefocused

ACME LSP: C++ via CCLS

Build CCLS

Download pre-built LLVM binaries for Darwin using curl:

; cd $HOME
; mkdir -p lib/llvm
; cd lib/llvm
; curl https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.1/clang+llvm-10.0.1-x86_64-apple-darwin.tar.xz --silent --location --output clang+llvm-10.0.1-x86_64-apple-darwin.tar.xz
; open clang+llvm-10.0.1-x86_64-apple-darwin.tar.xz

Get get dependencies via Homebrew:

; brew install rapidjson

Get fork of ccls from GitHub:

; mkdir -p $HOME/src/priv/git
; cd $HOME/src/priv/git
; git clone https://github.com/1g0rb0hm/ccls
; cd ccls
; git remote add upstream https://github.com/MaskRay/ccls

Build fork of ccls:

; cd $HOME/src/priv/git/ccls
; cmake -H. -BRelease -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$home/lib/llvm/clang+llvm-10.0.1-x86_64-apple-darwin/lib/cmake
; cmake --build Release

Add ccls to PATH:

; cd $HOME/bin
; ln -s $HOME/src/priv/git/ccls/Release/ccls

Configure ACME LSP

Determine ACME LSP configuration file location and edit it:

; acme-lsp -showconfig | head -1 | cut -f 2 -d : -
 $HOME/Library/Application Support/acme-lsp/config.toml
; E '$HOME/Library/Application Support/acme-lsp/config.toml'

A configuration that works with Xcode or CommandLineTools depending on your needs (NOTE ensure to set th path to your project workspace and replace $HOME with the full path to your home folder):

HideDiagnostics = true
RPCTrace = false

WorkspaceDirectories = [
  "$HOME/src/YOUR_C++_PROJECT"
]

[Servers]

[Servers.ccls]
  Command = [
  	 "ccls",
  # "-log-file=/Users/igor/Library/Logs/ccls.log",
  # "-v=4",
  # clang.extraArgs from 'clang -v -fsyntax-only -x c++ /dev/null'
  # clang.resourceDir from 'clang -print-resource-dir'
  #
  # -- Xcode
  # ; sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
  "--init={\"cache\":{\"directory\":\"$HOME/Library/Caches/ccls\"},\"highlight\":{\"enabled\":true},\"clang\":{\"extraArgs\":[\"-isystem\",\"/usr/local/include\",\"-isystem\",\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1\",\"-isystem\",\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/12.0.0/include\",\"-isystem\",\"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include\",\"-isystem\",\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include\",\"-isystem\",\"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks\"],\"resourceDir\":\"$HOME/lib/llvm/clang+llvm-10.0.1-x86_64-apple-darwin\"},\"completion\":{\"detailedLabel\":false}}"

  # -- CommandLineTools
  # ; sudo xcode-select -s /Library/Developer/CommandLineTools
  # "--init={\"cache\":{\"directory\":\"$HOME/Library/Caches/ccls\"},\"highlight\":{\"enabled\":true},\"clang\":{\"extraArgs\":[\"-isystem\",\"/usr/local/include\",\"-isystem\",\"/Library/Developer/CommandLineTools/usr/include/c++/v1\",\"-isystem\",\"/Library/Developer/CommandLineTools/usr/lib/clang/11.0.3/include\",\"-isystem\",\"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include\",\"-isystem\",\"/Library/Developer/CommandLineTools/usr/include\",\"-isystem\",\"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks\"],\"resourceDir\":\"$HOME/lib/llvm/clang+llvm-10.0.1-x86_64-apple-darwin\"},\"completion\":{\"detailedLabel\":false}}"
  ]
  StderrFile = "ccls.stderr.log"
  LogFile = "ccls.log"

[[FilenameHandlers]]
Pattern = '(\.h)|(\.c)|(\.cpp)|(\.cc)|(\.def)$'
ServerKey = "ccls"