ruby-lsp-slim 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c3e8a5ae3d3e90f420d313250c86e1b2ecffda0532d51058e82d94c571170419
4
+ data.tar.gz: 8a87834930733a4ac9e27ef782cf788eb77cc18664925f77576a66d63e498e17
5
+ SHA512:
6
+ metadata.gz: ef57f5abed3a7fde8abe4887f2753e3d8a0c7cfce3f27a72f1c63649f92804c4d740a4c763f5f16117d01ad0173cd03806f104c871756911415bdc9dcfa508df
7
+ data.tar.gz: 31d46434beec784235683d91e1ed1e949162b254281d1dff1c565822456e5f3cf488b249c3d8a44a28802467e501437dc4f22ab5000ee73864532dc8d3cb35d5
data/.rubocop.yml ADDED
@@ -0,0 +1,34 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
6
+ Style/StringLiterals:
7
+ EnforcedStyle: double_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
14
+
15
+ Metrics/MethodLength:
16
+ Enabled: false
17
+
18
+ Metrics/AbcSize:
19
+ Enabled: false
20
+
21
+ Metrics/CyclomaticComplexity:
22
+ Enabled: false
23
+
24
+ Metrics/PerceivedComplexity:
25
+ Enabled: false
26
+
27
+ Metrics/ClassLength:
28
+ Enabled: false
29
+
30
+ Style/Documentation:
31
+ Enabled: false
32
+
33
+ Naming/PredicateMethod:
34
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial release
6
+ - Support for hover, go-to-definition, completion, document symbols, diagnostics, and semantic highlighting in Slim templates
7
+ - Handles control code (`-`), output (`=`, `==`), interpolation (`#{}`), ruby filter, comments, text blocks, and backslash continuation
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "minitest", "~> 5.0"
8
+ gem "rake", "~> 13.0"
9
+ gem "rubocop", "~> 1.0"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Andrea Fomera
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Ruby LSP Slim
2
+
3
+ A [Ruby LSP](https://github.com/Shopify/ruby-lsp) addon that provides language server features for [Slim](http://slim-lang.com/) template files.
4
+
5
+ ## Features
6
+
7
+ - **Hover** — see method signatures and documentation
8
+ - **Go-to-definition** — jump to method/class definitions from Slim templates
9
+ - **Completion** — autocomplete Ruby methods and variables
10
+ - **Document symbols** — outline view for Ruby code in templates
11
+ - **Diagnostics** — syntax errors and warnings
12
+ - **Semantic highlighting** — rich syntax coloring for embedded Ruby
13
+
14
+ ## Installation
15
+
16
+ ### 1. Add the gem
17
+
18
+ Add `ruby-lsp-slim` to your project's `Gemfile`:
19
+
20
+ ```ruby
21
+ group :development do
22
+ gem "ruby-lsp-slim"
23
+ end
24
+ ```
25
+
26
+ Then run `bundle install`.
27
+
28
+ ### 2. Install the VS Code extension
29
+
30
+ Install the **Ruby LSP Slim** extension from the VS Code marketplace, or search for "Ruby LSP Slim" in the Extensions panel.
31
+
32
+ The extension runs a dedicated Ruby LSP server for `.slim` files, so all standard Ruby LSP features work inside your templates.
33
+
34
+ ### 3. Restart VS Code
35
+
36
+ After installing both the gem and extension, reload your VS Code window (`Cmd+Shift+P` → "Developer: Reload Window").
37
+
38
+ ## Supported Slim syntax
39
+
40
+ | Pattern | Example |
41
+ |---|---|
42
+ | Control code | `- x = 1` |
43
+ | Output | `= link_to "Home", root_path` |
44
+ | Unescaped output | `== raw_html` |
45
+ | Tag output | `h1= title` |
46
+ | Interpolation | `p Hello #{user.name}` |
47
+ | Ruby filter | `ruby:` block |
48
+ | Comments | `/ comment` |
49
+ | Text blocks | `\| text content` |
50
+ | Backslash continuation | `- x = 1 + \` (continues next line) |
51
+
52
+ ## Requirements
53
+
54
+ - Ruby >= 3.0.0
55
+ - ruby-lsp >= 0.26.0, < 1.0
56
+ - slim >= 4.0
57
+
58
+ ## Development
59
+
60
+ ```bash
61
+ bundle install
62
+ bundle exec rake test
63
+ ```
64
+
65
+ ## License
66
+
67
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << "test"
7
+ t.libs << "lib"
8
+ t.test_files = FileList["test/**/*_test.rb"]
9
+ end
10
+
11
+ require "rubocop/rake_task"
12
+
13
+ RuboCop::RakeTask.new
14
+
15
+ task default: %i[test rubocop]
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "ruby-lsp"
6
+ gem "ruby-lsp-slim", path: "../.."
7
+ gem "slim"
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationHelper
4
+ def link_to(text, url)
5
+ "<a href=\"#{url}\">#{text}</a>"
6
+ end
7
+
8
+ def pluralize(count, singular, plural = nil)
9
+ word = count == 1 ? singular : (plural || "#{singular}s")
10
+ "#{count} #{word}"
11
+ end
12
+
13
+ def time_ago_in_words(time)
14
+ seconds = Time.now - time
15
+ case seconds
16
+ when 0..59 then "just now"
17
+ when 60..3599 then "#{(seconds / 60).to_i} minutes ago"
18
+ when 3600..86_399 then "#{(seconds / 3600).to_i} hours ago"
19
+ else "#{(seconds / 86_400).to_i} days ago"
20
+ end
21
+ end
22
+
23
+ def current_user
24
+ User.new(name: "Alice", email: "alice@example.com", role: "admin")
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User
4
+ attr_accessor :name, :email, :role, :created_at
5
+
6
+ def initialize(name:, email:, role: "member", created_at: Time.now)
7
+ @name = name
8
+ @email = email
9
+ @role = role
10
+ @created_at = created_at
11
+ end
12
+
13
+ def admin?
14
+ role == "admin"
15
+ end
16
+
17
+ def display_name
18
+ "#{name} (#{email})"
19
+ end
20
+
21
+ def self.all
22
+ [
23
+ new(name: "Alice", email: "alice@example.com", role: "admin"),
24
+ new(name: "Bob", email: "bob@example.com"),
25
+ new(name: "Charlie", email: "charlie@example.com")
26
+ ]
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ h1 Welcome to the Slim App
2
+
3
+ - if current_user
4
+ p Hello, #{current_user.display_name}!
5
+
6
+ - if current_user.admin?
7
+ .admin-panel
8
+ h2 Admin Dashboard
9
+ p You have admin access.
10
+ = link_to "Manage Users", "/users"
11
+ - else
12
+ p You are logged in as a regular user.
13
+ - else
14
+ p Please sign in to continue.
15
+ = link_to "Sign In", "/login"
16
+
17
+ section.features
18
+ h2 Features
19
+ ul
20
+ - ["Hover", "Go to Definition", "Completion", "Diagnostics"].each do |feature|
21
+ li= feature
22
+
@@ -0,0 +1,3 @@
1
+ <%= link_to "Home", "/" %>
2
+ <%= current_user.name %>
3
+ <%= User.all %>
@@ -0,0 +1,17 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title My Slim App
5
+ meta charset="utf-8"
6
+ body
7
+ header
8
+ nav
9
+ ul
10
+ li
11
+ a href="/" Home
12
+ li
13
+ a href="/users" Users
14
+ main
15
+ == yield
16
+ footer
17
+ p Copyright #{Time.now.year} My App
@@ -0,0 +1,24 @@
1
+ h1 Users
2
+
3
+ - users = User.all
4
+
5
+ p= pluralize(users.length, "user")
6
+
7
+ table
8
+ thead
9
+ tr
10
+ th Name
11
+ th Email
12
+ th Role
13
+ th Joined
14
+ tbody
15
+ - users.each do |user|
16
+ tr
17
+ td= user.name
18
+ td= user.email
19
+ td= user.role
20
+ td= time_ago_in_words(user.created_at)
21
+
22
+ - if current_user.admin?
23
+ .actions
24
+ = link_to "Add User", "/users/new"
@@ -0,0 +1,32 @@
1
+ - user = User.all.first
2
+
3
+ h1= user.display_name
4
+
5
+ .user-profile
6
+ dl
7
+ dt Email
8
+ dd= user.email
9
+
10
+ dt Role
11
+ dd= user.role
12
+
13
+ dt Member Since
14
+ dd= user.created_at.strftime("%B %d, %Y")
15
+
16
+ - if user.admin?
17
+ .badge Admin
18
+
19
+ ruby:
20
+ stats = {
21
+ posts: 42,
22
+ comments: 128,
23
+ likes: 256
24
+ }
25
+
26
+ h2 Activity
27
+ dl
28
+ - stats.each do |label, count|
29
+ dt= label.to_s.capitalize
30
+ dd= count
31
+
32
+ = link_to "Back to Users", "/users"
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_lsp/addon"
4
+ require_relative "slim_document"
5
+ require_relative "version"
6
+
7
+ module RubyLsp
8
+ module RubyLspSlim
9
+ class Addon < ::RubyLsp::Addon
10
+ def activate(global_state, outgoing_queue)
11
+ @outgoing_queue = outgoing_queue
12
+
13
+ RubyLsp::Store.prepend(StorePatch) unless RubyLsp::Store.ancestors.include?(StorePatch)
14
+ RubyLsp::Server.prepend(ServerPatch) unless RubyLsp::Server.ancestors.include?(ServerPatch)
15
+
16
+ register_slim_capability if global_state.client_capabilities.supports_watching_files
17
+
18
+ outgoing_queue << Notification.window_log_message(
19
+ "[Ruby LSP Slim] Addon v#{::RubyLspSlim::VERSION} activated",
20
+ type: Constant::MessageType::INFO
21
+ )
22
+ end
23
+
24
+ def deactivate; end
25
+
26
+ def name
27
+ "Ruby LSP Slim"
28
+ end
29
+
30
+ def version
31
+ ::RubyLspSlim::VERSION
32
+ end
33
+
34
+ private
35
+
36
+ def register_slim_capability
37
+ registration = Request.new(
38
+ id: "ruby-lsp-slim-register",
39
+ method: "client/registerCapability",
40
+ params: Interface::RegistrationParams.new(
41
+ registrations: [
42
+ Interface::Registration.new(
43
+ id: "ruby-lsp-slim-text-sync",
44
+ method: "textDocument/didOpen",
45
+ register_options: Interface::TextDocumentRegistrationOptions.new(
46
+ document_selector: [
47
+ { language: "slim" }
48
+ ]
49
+ )
50
+ ),
51
+ Interface::Registration.new(
52
+ id: "ruby-lsp-slim-did-change",
53
+ method: "textDocument/didChange",
54
+ register_options: Interface::TextDocumentChangeRegistrationOptions.new(
55
+ document_selector: [
56
+ { language: "slim" }
57
+ ],
58
+ sync_kind: Constant::TextDocumentSyncKind::INCREMENTAL
59
+ )
60
+ ),
61
+ Interface::Registration.new(
62
+ id: "ruby-lsp-slim-did-close",
63
+ method: "textDocument/didClose",
64
+ register_options: Interface::TextDocumentRegistrationOptions.new(
65
+ document_selector: [
66
+ { language: "slim" }
67
+ ]
68
+ )
69
+ )
70
+ ]
71
+ )
72
+ )
73
+ @outgoing_queue << registration
74
+ end
75
+ end
76
+
77
+ module StorePatch
78
+ def get(uri)
79
+ super
80
+ rescue Store::NonExistingDocumentError
81
+ path = uri.to_standardized_path
82
+ raise unless path && File.extname(path) == ".slim" && File.file?(path)
83
+
84
+ set(uri: uri, source: File.binread(path), version: 0, language_id: :slim)
85
+ @state[uri.to_s]
86
+ end
87
+
88
+ def set(uri:, source:, version:, language_id:)
89
+ if language_id == :slim
90
+ @state[uri.to_s] = SlimDocument.new(
91
+ source: source,
92
+ version: version,
93
+ uri: uri,
94
+ global_state: @global_state
95
+ )
96
+ else
97
+ super
98
+ end
99
+ end
100
+ end
101
+
102
+ module ServerPatch
103
+ def text_document_did_open(message)
104
+ text_document = message.dig(:params, :textDocument)
105
+ uri = text_document[:uri]
106
+ path = uri.is_a?(URI::Generic) ? uri.to_standardized_path : uri.to_s
107
+
108
+ if text_document[:languageId] == "slim" || (path && File.extname(path) == ".slim")
109
+ @store.set(
110
+ uri: uri,
111
+ source: text_document[:text],
112
+ version: text_document[:version],
113
+ language_id: :slim
114
+ )
115
+
116
+ document = @store.get(uri)
117
+ if document.past_expensive_limit? && uri.respond_to?(:scheme) && uri.scheme == "file"
118
+ send_message(
119
+ Notification.new(
120
+ method: "window/logMessage",
121
+ params: Interface::LogMessageParams.new(
122
+ type: Constant::MessageType::WARNING,
123
+ message: "The file #{path} is too long. Semantic highlighting and diagnostics will be disabled."
124
+ )
125
+ )
126
+ )
127
+ end
128
+ else
129
+ super
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "slim_scanner"
4
+
5
+ module RubyLsp
6
+ module RubyLspSlim
7
+ class SlimDocument < RubyLsp::ERBDocument
8
+ def parse!
9
+ return false unless @needs_parsing
10
+
11
+ @needs_parsing = false
12
+ scanner = SlimScanner.new(@source)
13
+ scanner.scan
14
+ @host_language_source = scanner.host_language
15
+ @parse_result = Prism.parse_lex(scanner.ruby, partial_script: true)
16
+ @code_units_cache = @parse_result.code_units_cache(@encoding)
17
+ true
18
+ rescue StandardError => e
19
+ @host_language_source = +""
20
+ @parse_result = Prism.parse_lex("", partial_script: true)
21
+ @code_units_cache = @parse_result.code_units_cache(@encoding)
22
+ $stderr.puts("[Ruby LSP Slim] Parse error: #{e.message}")
23
+ true
24
+ end
25
+
26
+ def language_id
27
+ # Return :erb so that ruby-lsp's Definition listener applies the same
28
+ # "Object" receiver-type fallback it uses for ERB templates, allowing
29
+ # bare method calls like `current_user` to resolve via the index.
30
+ :erb
31
+ end
32
+ end
33
+ end
34
+ end