Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ELS – The Ember Language Server
Search
Tobias Bieniek
October 11, 2018
Programming
0
260
ELS – The Ember Language Server
Tobias Bieniek
October 11, 2018
Tweet
Share
More Decks by Tobias Bieniek
See All by Tobias Bieniek
Please wait… Oh, it didn't work!
turbo87
0
220
Abstract Syntax Forestry
turbo87
0
63
Help! How do you let others know what’s going on when you’re 8000 feet up in the air in a plane without an engine?!
turbo87
0
110
Internationali[sz]ation: It's easy in Ember!
turbo87
1
410
EuregioCup 2018 - Selbstbriefing
turbo87
1
64
The Next Generation of Testing in Ember.js
turbo87
3
1k
750 km FAI-Dreieck mit der Clubklasse
turbo87
3
66
High Level DOM Assertions for QUnit
turbo87
0
130
EuregioCup 2017 - Selbstbriefing
turbo87
1
45
Other Decks in Programming
See All in Programming
エンジニアのための”最低限いい感じ”デザイン入門
shunshobon
0
130
サーバーサイドのビルド時間87倍高速化
plaidtech
PRO
0
500
Oracle Database Technology Night 92 Database Connection control FAN-AC
oracle4engineer
PRO
1
210
AI OCR API on Lambdaを Datadogで可視化してみた
nealle
0
180
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
180
【第4回】関東Kaggler会「Kaggleは執筆に役立つ」
mipypf
0
840
コンテキストエンジニアリング Cursor編
kinopeee
1
710
DockerからECSへ 〜 AWSの海に出る前に知っておきたいこと 〜
ota1022
5
1.8k
Flutterと Vibe Coding で個人開発!
hyshu
1
270
250830 IaCの選定~AWS SAMのLambdaをECSに乗り換えたときの備忘録~
east_takumi
0
180
マイコンでもRustのtestがしたい その2/KernelVM Tokyo 18
tnishinaga
2
2.3k
Terraform やるなら公式スタイルガイドを読もう 〜重要項目 10選〜
hiyanger
13
3.2k
Featured
See All Featured
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
44
2.5k
Agile that works and the tools we love
rasmusluckow
329
21k
How to Think Like a Performance Engineer
csswizardry
25
1.8k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
9
780
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
50
5.5k
Building a Modern Day E-commerce SEO Strategy
aleyda
43
7.5k
Unsuck your backbone
ammeep
671
58k
Why Our Code Smells
bkeepers
PRO
338
57k
Reflections from 52 weeks, 52 projects
jeffersonlam
351
21k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
23
1.4k
Git: the NoSQL Database
bkeepers
PRO
431
65k
Transcript
ELS the Ember Language Server
Turbo87 TobiasBieniek
simplabs based in Munich consulting all over ! * Stickers
available after the talk
Disclaimer this talk contains simplified code examples! the real code
is available at https://github.com/emberwatch/ember-language-server
What text editor do you use? " # $ %
$ & ' ( ) * " *
None
Goto Class
Goto Definition
IntelliJ-only
vim Emacs VS Code IntelliJ JavaScript ❓ ❓ ❓ ❓
CSS ❓ ❓ ❓ ❓ Ember.js ❓ ❓ ❓ ❓ Rust ❓ ❓ ❓ ❓
JavaScript ✅ CSS ✅ Ember.js ✅ Rust ✅ vim ✅
Emacs ✅ VS Code ✅ IntelliJ ✅
JavaScript javascript- typescript- langserver CSS vscode-css- languageserver Ember.js ember-language- server
Rust rust-language- server vim vim-lsp Emacs emacs-lsp VS Code builtin! IntelliJ
HTTP JSON API
ELS JSON-RPC Language Server Protocol HTTP JSON API
JSON-RPC 2.0 Request { "jsonrpc": "2.0", "id": 42, "method": "add",
"params": { "a": 1, "b": 2, }, } Response { "jsonrpc": "2.0", "id": 42, "result": 3, "error": null, }
vscode-languageserver
Syntax Highlighting
Syntax Highlighting (sorry, not responsible for that...) https://github.com/Microsoft/language-server-protocol/issues/33#issuecomment-231883169
Autocomplete
Request { "method": "textDocument/completion", "params": { "textDocument": { "uri": "file:
///Users/tbieniek/Code/crates.io/app/templates/index.hbs", }, "position": { "line": 5, "character": 18, }, }, }
import URI from 'vscode-uri'; let uri = URI.parse( 'file: ///Users/tbieniek/Code/crates.io/app/templates/index.hbs'
) let path = uri.fsPath; // -> /Users/tbieniek/Code/crates.io/app/templates/index.hbs
import fs from 'fs'; let content = fs.readFileSync( '/Users/tbieniek/Code/crates.io/app/templates/index.hbs', {
encoding: 'utf-8' }, ); let lines = content.split('\n'); let line = line[5]; let character = line[18]; // -> a
import fs from 'fs'; import { preprocess } from '@glimmer/syntax';
let content = fs.readFileSync( '/Users/tbieniek/Code/crates.io/app/templates/index.hbs', { encoding: 'utf-8' }, ); let ast = preprocess(content); // -> Abstract Syntax T ree
{{button onclick=(a)}} Program -> MustacheStatement -> PathExpression -> Hash ->
HashPair -> SubExpression -> PathExpression
{{button onclick=(a)}} Program -> MustacheStatement -> PathExpression -> Hash ->
HashPair -> SubExpression -> PathExpression
{{button onclick=(a)}} Program -> MustacheStatement -> PathExpression -> Hash ->
HashPair -> SubExpression -> PathExpression
{{button onclick=(a)}} Program -> MustacheStatement -> PathExpression -> Hash ->
HashPair -> SubExpression -> PathExpression
{{button onclick=(a)}} Program -> MustacheStatement -> PathExpression -> Hash ->
HashPair -> SubExpression -> PathExpression
{{button onclick=(a)}} Program -> MustacheStatement -> PathExpression -> Hash ->
HashPair -> SubExpression -> PathExpression
{{button onclick=(a)}} Program -> MustacheStatement -> PathExpression -> Hash ->
HashPair -> SubExpression -> PathExpression
AST Explorer https://astexplorer.net
{{button onclick=(a)}} Program -> MustacheStatement -> PathExpression -> Hash ->
HashPair -> SubExpression -> PathExpression Cursor
path: { type: 'PathExpression', original: 'a', this: false, parts: [
'a', ], data: false, loc: { start: { line: 5, column: 18, }, end: { line: 5, column: 19, }, }, } Program -> MustacheStatement -> PathExpression -> Hash -> HashPair -> SubExpression -> PathExpression
path: { type: 'PathExpression', original: 'a', this: false, parts: [
'a', ], data: false, loc: { start: { line: 5, column: 18, }, end: { line: 5, column: 19, }, }, } position: { line: 5, character: 18, } Program -> MustacheStatement -> PathExpression -> Hash -> HashPair -> SubExpression -> PathExpression
let node = findNodeAtPosition(ast, position); let isSubExpressionPath = ( node.type
=== 'PathExpression' && node.parent.type === 'SubExpression' ); if (isSubExpressionPath) { return SUB_EXPRESSION_HELPERS; }
const SUB_EXPRESSION_HELPER_NAMES = [ 'action', 'component', 'concat', 'get', 'if', 'unless',
]; const SUB_EXPRESSION_HELPERS = SUB_EXPRESSION_HELPER_NAMES .map(name => ({ label: name, kind: CompletionItemKind.Function, }));
Autocomplete ✅
Autocomplete #2
import path from 'path'; import findUp from 'find-up'; let projectRoot
= findUp.sync('ember-cli-build.js', { cwd: path.dirname( '/Users/tbieniek/Code/crates.io/app/templates/index.hbs' ), }); // -> /Users/tbieniek/Code/crates.io
import glob from 'fast-glob'; let components = glob.sync(['**/*.js'], { cwd:
`${projectRoot}/app/components`, }); let templates = glob.sync(['**/*.hbs'], { cwd: `${projectRoot}/app/templates/components`, });
import glob from 'fast-glob'; let components = glob.sync(['**/*.js'], { cwd:
`${projectRoot}/app/components`, }); let templates = glob.sync(['**/*.hbs'], { cwd: `${projectRoot}/app/templates/components`, }); let names = components .concat(templates) .map(filePath => stripExtension(filePath));
import glob from 'fast-glob'; let components = glob.sync(['**/*.js'], { cwd:
`${projectRoot}/app/components`, }); let templates = glob.sync(['**/*.hbs'], { cwd: `${projectRoot}/app/templates/components`, }); let names = components .concat(templates) .map(filePath => stripExtension(filePath)); let uniqueNames = Array.from(new Set(names));
let names = components .concat(templates) .map(filePath => stripExtension(filePath)); let uniqueNames
= Array.from(new Set(names)); return uniqueNames.map(name => ({ label: name, kind: CompletionItemKind.Class, }));
Autocomplete #2 ✅
Goto Definition
Request { "method": "textDocument/definition", "params": { "textDocument": { "uri": "file:
///Users/tbieniek/Code/crates.io/app/templates/crate.hbs", }, "position": { "line": 92, "character": 14, }, }, }
{{crate-readme rendered=crate.readme}} Program -> MustacheStatement -> PathExpression -> Hash ->
HashPair -> SubExpression -> PathExpression
let node = findNodeAtPosition(ast, position); let isMustacheStatementPath = ( node.type
=== 'PathExpression' && node.parent.type === 'MustacheStatement' ); if (!isMustacheStatementPath) { return; }
import path from 'path'; import findUp from 'find-up'; let projectRoot
= findUp.sync('ember-cli-build.js', { cwd: path.dirname( '/Users/tbieniek/Code/crates.io/app/templates/crate.hbs' ), }); // -> /Users/tbieniek/Code/crates.io
import URI from 'vscode-uri'; let componentPath = `${projectRoot}/app/components/${node.original}.js`; if (fs.existsSync(componentPath))
{ results.push({ uri: URI.file(componentPath), range: { start: ..., end: ... }, }); }
What else can we build with this?
{{#link-to "index"}}Home{{/link-to}} • app/controllers/index.js • app/routes/index.js • app/templates/index.hbs
import DS from 'ember-data'; export default DS.Model.extend({ crates: DS.hasMany('crate'), });
• app/adapters/crate.js • app/models/crate.js • app/serializers/crate.js
import Component from '@ember/component'; import { inject as service }
from '@ember/service'; export default Component.extend({ session: service('session'), }); • app/services/session.js
{{t "my-name-is"}} • translations/de.json • translations/en.json • translations/fr.json • translations/nl.json
Caveats (roadblocks and deviations)
Compiler vs. Editor
Compiler > must produce 100% correct output > expects 100%
valid input
Editor > is allowed to produce incomplete output > should
handle incomplete/invalid input
@glimmer/syntax > written for the template compiler > can not
handle incomplete/invalid input {{#link-to "|
ELS Editor Support
ELS Editor Support > VSCode ... done (vscode-ember extension) >
Atom ... done (ide-ember package) > emacs ... work-in-progress > vim ... )
Wishlist (not yet supported by LSP)
File Type Icons
Goto Related File from the crates controller
Goto Related File > cycle through controller, route, template >
or component and template > or model, adapter, serializer > or to the related test file
Inline Translations
emberwatch/ember-language-server #topic-editors
ELS the Ember Language Server Thanks!