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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
230
Abstract Syntax Forestry
turbo87
0
66
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
420
EuregioCup 2018 - Selbstbriefing
turbo87
1
69
The Next Generation of Testing in Ember.js
turbo87
3
1k
750 km FAI-Dreieck mit der Clubklasse
turbo87
3
72
High Level DOM Assertions for QUnit
turbo87
0
130
EuregioCup 2017 - Selbstbriefing
turbo87
1
52
Other Decks in Programming
See All in Programming
AI Agent の開発と運用を支える Durable Execution #AgentsInProd
izumin5210
7
2.2k
なぜSQLはAIぽく見えるのか/why does SQL look AI like
florets1
0
430
疑似コードによるプロンプト記述、どのくらい正確に実行される?
kokuyouwind
0
370
AI 駆動開発ライフサイクル(AI-DLC):ソフトウェアエンジニアリングの再構築 / AI-DLC Introduction
kanamasa
11
6.2k
re:Invent 2025 トレンドからみる製品開発への AI Agent 活用
yoskoh
0
720
CSC307 Lecture 04
javiergs
PRO
0
650
コマンドとリード間の連携に対する脅威分析フレームワーク
pandayumi
1
440
0→1 フロントエンド開発 Tips🚀 #レバテックMeetup
bengo4com
0
530
React 19でつくる「気持ちいいUI」- 楽観的UIのすすめ
himorishige
11
5.9k
Implementation Patterns
denyspoltorak
0
270
フルサイクルエンジニアリングをAI Agentで全自動化したい 〜構想と現在地〜
kamina_zzz
0
400
2年のAppleウォレットパス開発の振り返り
muno92
PRO
0
200
Featured
See All Featured
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
1.7k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
1
1.4k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
270
My Coaching Mixtape
mlcsv
0
45
Raft: Consensus for Rubyists
vanstee
141
7.3k
Mobile First: as difficult as doing things right
swwweet
225
10k
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
160
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Become a Pro
speakerdeck
PRO
31
5.8k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
54k
Tell your own story through comics
letsgokoyo
1
800
AI: The stuff that nobody shows you
jnunemaker
PRO
2
230
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!