Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
ELS – The Ember Language Server
Search
Tobias Bieniek
October 11, 2018
Programming
270
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
ELS – The Ember Language Server
Tobias Bieniek
October 11, 2018
More Decks by Tobias Bieniek
See All by Tobias Bieniek
Please wait… Oh, it didn't work!
turbo87
0
240
Abstract Syntax Forestry
turbo87
0
74
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
140
Internationali[sz]ation: It's easy in Ember!
turbo87
1
440
EuregioCup 2018 - Selbstbriefing
turbo87
1
77
The Next Generation of Testing in Ember.js
turbo87
3
1.1k
750 km FAI-Dreieck mit der Clubklasse
turbo87
3
85
High Level DOM Assertions for QUnit
turbo87
0
140
EuregioCup 2017 - Selbstbriefing
turbo87
1
61
Other Decks in Programming
See All in Programming
KiroのSpecで「五目並べ」を作ってみる
satoshi256kbyte
1
380
Apple Intelligence を用いた個人情報誤送信防止、及びユーザーリクエスト体験の改善について
yukiny
0
350
Long Living Tokens, Die, Die, Die! (or: The One Ring is Banned in Modern)
sylph01
0
310
R8 の設定見直しでアプリサイズ削減
andpad
0
220
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
290
HTML-in-Canvasで作る未来のWeb UI表現
clockmaker
5
5.1k
現場と育てるRailsモデリング 〜Spreadsheetから始めたモデル設計〜
siroemk
2
600
Next.js: The final boss of Web Performance
guaca
0
260
Everything will be SERVERLESS — 信じて運用した10年の経験値 / Everything Will be Serverless — Lessons Learned from 10 Years of Operational Experience
seike460
PRO
1
1.1k
AWSに止められる覚悟してますか?
morizo_1984
2
540
ソニーのクラウド共通基盤の変遷とAI時代の開発スタイルに合わせた進化 / The Journey of Sony’s Common Cloud Platform and Its Evolution for AI-Native Development
kenjiyoneyama
0
270
[2026-09-26]空論ジェネリックプロセス~テスト資産とAIで紡ぐ、再現可能なパフォーマンスチューニングの話~
tosite
0
270
Featured
See All Featured
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Building a Scalable Design System with Sketch
lauravandoore
464
34k
Git: the NoSQL Database
bkeepers
PRO
433
67k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
35
2.9k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
68
59k
The Language of Interfaces
destraynor
162
27k
WCS-LA-2024
lcolladotor
0
890
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
730
Designing Powerful Visuals for Engaging Learning
tmiket
1
610
GitHub's CSS Performance
jonrohan
1033
470k
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Skip the Path - Find Your Career Trail
mkilby
1
260
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!