Skip to content

Add support for slim files #1233

Open
Open

Description

@omarluq

I have checked that this feature is not already implemented

  • This feature does not exist

Use case

Slim is a fairly popular choice among ruby/rails devs and adding support for it is actually not a lot of work once ERB support is merged

Description

Ruby intellisense for slim printing lines, none-printing lines and ruby code blocks

ruby:
 print_msg = true

- if print_msg
  = 'hello world'.capitalize

Implementation

Following the same Idea in issue#1055 the LSP can require slim gem from the main bundle and provide the feature only if it's found
As for parsing slim, The lsp doesn't have to worry about that!
instead it can use Slim::ERBConverter.new(options).call(slim_code) which outputs erb code

Activity

  1. added
    pinnedThis issue or pull request is pinned and won't be marked as stale
    on Dec 4, 2023
  2. omarluq commented on Dec 5, 2023

    @omarluq
    Author

    @vinistock can you assign me this issue?
    I would like to take a swing at implementing it once #1184 and Shopify/vscode-ruby-lsp#896 are merged if that's ok!
    and do you have any feedbacks on my suggested approach?

  3. vinistock commented on Dec 5, 2023

    @vinistock
    Member

    Thank you for the feature suggestion. Does Slim provide a way to extract just the Ruby code while maintaining the correct line and column information?

    Ideally, we wouldn't be converting Slim into ERB and then ERB into Ruby, so that we can finally parse the Ruby code. That will be very slow for large files.

    That is currently the biggest blocker with ERB support and we can't move forward until we figure that part out.

  4. omarluq commented on Dec 5, 2023

    @omarluq
    Author

    The slim gem ships a parses but it doesn't give the line numbers, however it gives a syntax tree in the form of a nested array
    so the following example

    ruby:
      print_value = true
    
    - if print_value
      p = 'Hello World'.capatlize

    would generate this output when put through the slim parser

    # [
    #   [:multi,
    #     [
    #       [:slim, :embedded, "ruby",
    #         [:multi,
    #           [:newline],
    #           [:slim, :interpolate, "print_value = true"],
    #           [:newline]
    #         ]
    #       ],
    #       [:newline],
    #       [:slim, :control, "if print_value",
    #         [:multi,
    #           [:newline],
    #           [:html, :tag, "p", [:html, :attrs],
    #             [:slim, :output, true, "'Hello World'.capitalize",
    #               [:multi,
    #                 [:newline]
    #               ]
    #             ]
    #           ]
    #         ]
    #       ]
    #     ]
    #   ]
    # ]
    

    Maybe traverse the tree and derive the line numbers and column numbers for the ruby code

  5. vinistock commented on Dec 6, 2023

    @vinistock
    Member

    I don't believe that AST will allow using Ruby LSP features. All of our features are based on parsing Ruby with Prism and analyzing the AST.

    For that to work properly, we need slim to be able to extract only the Ruby code from templates maintaining their exact line and column positions.

    Otherwise, things like semantic highlighting would highlight the wrong range for tokens. Or definition wouldn't be able to find the target based on the position received from the editor.

    We would need slim to be able to take a template like this

    ruby:
      print_value = true
    
    - if print_value
      p = 'Hello World'.capatlize
    

    And return only the Ruby code in its exact positions

      print_value = true
    
      if print_value
          'Hello World'.capatlize

    Then we can just take that Ruby code, parse it with Prism (which will give us the right line and column information) and all features will just work out of the box.

  6. omarluq commented on Dec 7, 2023

    @omarluq
    Author

    @vinistock slim doesn't give us that out of the box, that being said its not really hard to derive a ruby document we just traverse the AST.

    require 'slim'
    require 'pry'
    
    def traverse_sexp(sexp, indent = 0, is_root = true)
      code = []
      factor = indent.zero? ? 1 : indent
      indent_space = "  " * factor
    
      sexp.each do |node|
        next unless node.is_a?(Array)
    
        case node.first
        when :multi
          code << traverse_sexp(node[1..-1], indent, is_root)
        when :slim
          case node[1]
          when :embedded
            code << traverse_sexp(node[3], is_root ? 0 : indent + 1, false)
          when :interpolate, :control, :output
            code << indent_space + node[2].strip
            code << traverse_sexp(node[3], indent + 1, false) if node.length > 3
          end
        when :html
          extra_whitespaces = convert_html_to_whitespaces(node)
          code << indent_space + extra_whitespaces if extra_whitespaces
          if node[4].is_a?(Array) && node[4][0] == :slim
            if [:output, :interpolate].include?(node[4][1])
              index = node[4][1] == :output ? 3 : 2
              code << indent_space + node[4][index].strip + "\n"
            else
              code << traverse_sexp(node[4], indent, false)
            end
          elsif node[4].is_a?(Array) && node[4][0] != :slim
            code << traverse_sexp(node[4], indent, false)
          end
        when :newline
          code << "\n"
        end
      end
    
      code.join("")
    end
    
    def convert_html_to_whitespaces(html_node)
      case html_node[1]
      when :tag
        ' ' * html_node[2].to_s.length
      when :attrs
        html_node[2..-1].reduce(' ') do |whitespaces, attr_node|
          attr_format = attr_node[3][0]
          subindex = attr_format == :static ? 1 : 3
          ' ' * (atattr_nodetr[2].length + attr_node[3][subindex].length)
        end
      end
    end
    
    template1 = <<~SLIM
    - if items.any?
      table id=items class='table yellow'
      - for item in items
        tr
          td.name = item.name
          td.price = item.price
    - else
      p 'No items found.'
    SLIM
    
    template2 = <<~SLIM
    ruby:
      print_value = true
    
    - if print_value
      p = 'Hello World'.capitalize
    SLIM
    
    sexp1 = Slim::Parser.new.call(template1)
    sexp2 = Slim::Parser.new.call(template2)
    
    File.write('example1.rb', traverse_sexp(sexp1))
    File.write('example2.rb', traverse_sexp(sexp2))
      if items.any?
           
      for item in items
          
              item.name
              item.price
      else
         'No items found.'
    print_value = true
    
    if print_value
      'Hello World'.capitalize
  7. omarluq commented on Dec 7, 2023

    @omarluq
    Author

    ok so I got a little carried away and made small gem out of this traverse function https://github.com/omarluq/sx-processor

    this is for your eyes to review its way more optimized than the traverse function! ideally I would like to add the sx-processor (I'm open to renaming it) class to the ruby-lsp and add a SlimDocument class that uses it to generate the ruby blob for Prism

  8. vinistock commented on Dec 8, 2023

    @vinistock
    Member

    Sorry, but if we were to accept a runtime dependency, it would be only implicit (like we do for rubocop) and only for slim itself.

    I'm also wondering if this may be a better fit for addons. There are multiple templating engines out there and we probably don't want to maintain support for all of them inside this codebase.

    It's not currently possible to do it, but I'm thinking if we might be able to design an API to allow for this.

  9. omarluq commented on Dec 8, 2023

    @omarluq
    Author

    @vinistock Apologize for the misunderstanding I didn't mean adding the gem as a runtime dependency I just meant coding the class into the LSP it self, I made the gem for demo proposes, so you can review the approach Im proposing. that being said I do see your point. Im open to both.

  10. oheyjesse commented on Mar 17, 2026

    @oheyjesse

    Hello folks - I found this issue whilst researching the root cause of why .slim isn't fully supported (beyond syntax highlighting) for Intellisense etc in VSCode - I can see we added a help-wanted label in 2025, but I haven't seen any movement since then. Keen to see which direction it could move, if at all - as full .slim VSCode support would be awesome!

    I can see @omarluq has put together some example code that could potentially be used directly in Ruby-LSP (not as a dependency, as suggested), and it seems to have promise. @vinistock or other LSP contributors- Do we think a PR bringing this code into Ruby-LSP would be viable?

    Re: the addons approach: I noticed the link in this comment to addons no longer works (though I did find this - is this still relevant?) - Did said pre-requisite work in addons get completed? or is this blocked by said work?

  11. bjeanes commented on Nov 11, 2026

    @bjeanes

    Following @vinistock's suggestion above, I've written an add-on for this: ruby-lsp-slim-lang. It reads the template with Slim's parser and keeps only the Ruby, blanking everything else, which keeps each line and column where it was in the template. Ruby LSP then handles the template much like an ERB file, with go to definition, hover and completion working inside = and - lines, ruby: blocks and #{} interpolation.

    It isn't on RubyGems yet. If you'd like to try it, add it to your app's Gemfile:

    group :development do
      gem "ruby-lsp-slim-lang", github: "bjeanes/ruby-lsp-slim-lang", require: false
    end

    In Neovim, it works today once Neovim recognises .slim files and slim is in the file types Ruby LSP is attached to. The README has the config for both.

    VS Code needs a change to the extension, since it only sends Ruby and ERB files to the server. I've opened #4270 to add a rubyLsp.addonLanguageIds setting for that.

    The README lists the gaps I know about. For example, it only knows Slim's default shortcuts and attribute delimiters, since Ruby LSP doesn't boot your app. I expect there are templates it gets wrong that I haven't come across. Reports from anyone with a large Slim codebase would help most. Please open an issue on the repo if something doesn't resolve or the server reports errors in a template.

    The add-on API still doesn't have a way to register a document type. As such, the add-on patches Ruby LSP's document store to create its own, which depends on Ruby LSP's internals. That's why it pins ruby-lsp to >= 0.26, < 0.28.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp-wantedExtra attention is neededpinnedThis issue or pull request is pinned and won't be marked as stale

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions