• Stars
    star
    432
  • Rank 100,650 (Top 2 %)
  • Language
    Crystal
  • License
    MIT License
  • Created about 4 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Language Server Protocol implementation for Crystal. 🔮

crystalline

A Language Server for Crystal.

Build Status GitHub tag (latest SemVer) GitHub

Crystalline is an implementation of the Language Server Protocol written in and for the Crystal Language. It aims to provide limited language features (like go-to, autocompletion, syntax and semantic checking) and ease development with any compatible code editor.

Status: in development.


Installation

Recommended method is to download and use pre-built binaries when possible. Building from source does take a long time.

Compatibility

Crystal Crystalline
1.8.x 0.9
1.7 0.8
1.6 0.7
1.4 0.6
1.3 0.5
1.2 0.4
1.1 0.4
1.0 0.3
0.36 0.2
0.35.1 0.1

Pre-built binaries

Latest Release

Linux (x86_64)
wget https://github.com/elbywan/crystalline/releases/latest/download/crystalline_x86_64-unknown-linux-musl.gz -O crystalline.gz &&\
gzip -d crystalline.gz &&\
chmod u+x crystalline
ArchLinux
yay -S crystalline
MacOS (x86_64)
curl -L https://github.com/elbywan/crystalline/releases/latest/download/crystalline_x86_64-apple-darwin.gz -o crystalline.gz &&\
gzip -d crystalline.gz &&\
chmod u+x crystalline

Specific release

See the releases page.

Specific commit

Binaries are uploaded as artifacts during the CI build.

Build from source

Warning: this can take a long time! (several minutes - up to 20 minutes, depending on your hardware)

Scoped install

In the .shard.yml file:

development_dependencies:
  crystalline:
    github: elbywan/crystalline
    branch: master

Then:

# Produces a binary at ./bin/crystalline
shards build crystalline --release --no-debug --progress -Dpreview_mt

Global install

git clone https://github.com/elbywan/crystalline
cd crystalline
shards install
mkdir bin
crystal build ./src/crystalline.cr  -o ./bin/crystalline --release --no-debug --progress -Dpreview_mt

Known Build Issues

Potential errors when building from source.

llvm-config path

llvm is required in order to build crystalline, if you get the following error message it means that the crystal compiler is unable to locate the llvm-config binary:

--: : command not found
Showing last frame. Use --error-trace for full trace.

In /usr/local/Cellar/crystal/0.35.1/src/llvm/lib_llvm.cr:13:17

 13 | VERSION = {{`#{LibLLVM::LLVM_CONFIG} --version`.chomp.stringify}}
                  ^
Error: error executing command: "" --version, got exit status 127

This can be solved by adding the location of the llvm-config binary to the LLVM_CONFIG environment variable. (or the containing directory to the PATH env. variable)

For instance on a typical macOS setup, prefixing the command with the following declaration would solve the issue:

# Prepend the command with this:
env LLVM_CONFIG=/usr/local/opt/llvm/bin/llvm-config
# For Example:
env LLVM_CONFIG=/usr/local/opt/llvm/bin/llvm-config crystal build ./src/crystalline.cr  -o ./bin/crystalline --release --no-debug -Dpreview_mt

Replace env by export on Debian and derived (Ubuntu, Mint, ...)

ld: library not found for -llibxml2.tbd

LLVM 10.0.1 has some issues when reporting required system libraries on macOS.

More info: here

# Wrong: -llibxml2.tbd
$ llvm-config --system-libs
-lm -lz -lcurses -llibxml2.tbd
# `liblibxml2.tbd.dylib` is unlikely to be found during compilation,
# hence the "library not found" error…

A hacky solution until llvm produces a solution would be to add a symbolic link to the correct shared library file:

ln -s /usr/lib/libxml2.2.dylib /usr/local/lib/liblibxml2.tbd.dylib

Or just use a different LLVM major version until this issue is fixed upstream.

Usage

Crystalline is meant to be used alongside an editor extension.

VSCode

vscode screen

  • Reload the window by pressing CMD/CTRL + SHIFT + P and select Developer: Reload Window (or as an alternative, restart VSCode).

Vim/Neovim

Using Conquer of Completion we can configure Crystalline as our LSP backend and get all the features of Crystalline we would get with VSCode.

  • Download vim-crystal plugin.
  • Download CoC plugin.
  • Make sure crystalline binary is in your PATH.

Add the following snippet to your coc-settings.json file:

{
"languageserver": {
    "crystal": {
      "command": "crystalline",
      "args": [
        "--stdio"
      ],
      "filetypes": [
        "crystal"
      ],
      "rootPatterns": ["shard.yml"]
    }
  }
}

Emacs

  • Download the crystal-mode package.
  • Download the lsp-mode package.
  • Make sure crystalline binary is in your PATH.

At the moment, lsp-mode only knows about scry as the Crystal language server. So, to get it working with crystalline we need to configure lsp-mode to look for crystalline.

You can use the following config snippet to achieve this:

(with-eval-after-load 'lsp-mode
  (add-to-list 'lsp-language-id-configuration
               '(crystal-mode . "crystal"))
  (lsp-register-client
  (make-lsp-client :new-connection (lsp-stdio-connection '("crystalline"))
                   :activation-fn (lsp-activate-on "crystal")
                   :priority '1
                   :server-id 'crystalline)))

This will give higher priority to crystalline, and Emacs should automatically connect to the backend whenever you're in crystal-mode.

Entry point

Important: Crystalline will try to determine which file is best suited as an entry point when providing language features.

The default behaviour is to check the shard.yml file for a target entry with the same name as the shard.

name: my_shard

targets:
  my_shard:
    main: src/entry.cr

With the configuration above, every file required by src/entry.cr will use src/entry.cr as the entry point.

If this shard.yml entry is not present, or if the file is not part of the main dependency tree then crystalline will use the file itself as the entry point.

To override this behaviour, you can add a configuration key in the shard.yml file.

crystalline:
  main: .crystalline_main.cr

This can be extremely important to understand when you are writing a code library that does not call any of its own methods - it will skip code analysis. In this case, and if you are writing specs, you should point to a file that require the specs (or anything calling the library) and then crystalline will use it as the entry point.

# Contents of a file at the root of the project.
# Will require the specs that call the library methods and enable the code analysis.
require "./spec/**"

Features

Disclaimer: Crystalline is not as extensive in terms of features as other Language Servers but still provides very convenient tools.

Code Diagnostics

Syntax and semantic checks on save.

Limited Autocompletion

List (depending on the target) method definitions, macros or module/class/struct names or symbols available in the current context.

Formatting

A whole document or a text selection.

Go to definition

By clicking on a symbol with the Cmd or Ctrl key pressed (editor/platform dependent).

Hover information

Hovering should display (when possible) either a variable type, a function definition signature or the expanded macro.

Document symbols

Fetch all the symbols in a given file, used in VSCode to populate the Outline view and the Breadcrumbs.

Limitations

  • Memory usage is high due to the boehm GC behaviour and the crystal compiler itself. See: #23

  • Due to Crystal having a wide type inference system (which is incredibly convenient and practical), compilation times can unfortunately be relatively long for big projects and depending on the hardware. This means that the LSP will be stuck waiting for the compiler to finish before being able to provide a response. Crystalline tries to mitigate that by caching compilation outcome when possible.

  • Methods that are not called anywhere will not be analyzed, as this is how the Crystal compiler works.

  • The parser is not permissive, nor incremental which means that the features will sometimes not work. It would involve a massive amount of work to change that.

Development

Dev build

Sentry is used to re-build crystalline in debug mode on code change.

# To build sentry (once):
shards build --release sentry
# Then, to launch it and watch the filesystem:
./bin/sentry -i

Logs

Logging is the most practical way to debug the LSP.

# Use the LSP logger to display logs in the editor.
LSP::Log.info { "log" }

Debug logs are deactivated by default, uncomment this line in src/crystalline/lsp/server.cr to enable them:

# Uncomment:
# ::Log.setup(:debug, LSP::Log.backend.not_nil!)

Contributing

  1. Fork it (https://github.com/elbywan/crystalline/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Please always crystal tool format your code!

Contributors

Credit

  • Scry, the original LSP for Crystal has been a great source of inspiration. I also re-used tiny bits of code from there.
  • Icon made by Smashicons from www.flaticon.com.

Trivia

Why the name crystalline?

Aside of the obvious reasons (crystal-lang), cristaline is a famous bottled water brand in France that published silly TV commercials. It is pronounced the same as crystalline.

guy roux

More Repositories

1

wretch

A tiny wrapper built around fetch with an intuitive syntax. 🍬
TypeScript
4,692
star
2

yett

🔐A small webpage library to control the execution of (third party) scripts
JavaScript
769
star
3

bosket

Collection of tree view components for front-end frameworks. 🌳
JavaScript
450
star
4

hyperactiv

A super tiny reactive library. ⚡
JavaScript
436
star
5

cryomongo

A MongoDB driver written in pure Crystal. ❄️
Crystal
72
star
6

zap

Another [insert blazing fast synonyms] JavaScript package manager
Crystal
54
star
7

wretch-middlewares

Collection of middlewares for the Wretch library. 🎁
TypeScript
47
star
8

quadtree-lib

Efficient quadtrees library written in CoffeeScript.
CoffeeScript
46
star
9

moongoon

An object-document mapper for MongoDB. 🌙
Crystal
45
star
10

openapi-generator

An OpenAPI document generator. ⚙️
Crystal
22
star
11

bson.cr

A pure Crystal Implementation of the BSON Specification. 🐃
Crystal
19
star
12

spotify-zeroconf

Spotify Web API authentication, the easy way.
JavaScript
17
star
13

normaliz

A tiny library that normalizes data. 🏭
JavaScript
14
star
14

crystal-lsp

An implementation of the Language Server Protocol written in Crystal
Crystal
13
star
15

hyperactiv-hooks-demo

Hyperactiv hooks isomorphic demo with SSR.
JavaScript
5
star
16

htmltree

A quick and dirty html tree experiment. 💭
JavaScript
5
star
17

hermes-crystal

Crystal bindings to the Hermes protocol used by the Snips platform. 💎
Crystal
4
star
18

quickey

Shell commands at your fingertips ✌️
JavaScript
2
star
19

rescript-hyperactiv

Hyperactiv bindings for ReScript
ReScript
2
star
20

hermes-reason

ReasonML / Ocaml bindings to the Hermes protocol used by the Snips platform. 🐪
OCaml
2
star
21

ppx-ctypes-helper

Ppx that helps serializing structures and enums to / from c. 🏭
OCaml
2
star
22

Pendu

Ocaml hangman game (client/server)
OCaml
1
star
23

struct-mappings

Generate Crystal classes mapping C structures. 🗺️
Crystal
1
star
24

shannon-bindings

Shannon cipher node.js bindings
C
1
star
25

CTigre-to-MIPS-compiler

Compiler from CTigre to MIPS written in OCaml
OCaml
1
star