Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An alternative to vue-mode with polymode #109

Open
RKBK opened this issue Jan 13, 2021 · 18 comments
Open

An alternative to vue-mode with polymode #109

RKBK opened this issue Jan 13, 2021 · 18 comments

Comments

@RKBK
Copy link

RKBK commented Jan 13, 2021

I've had several issues getting vue-mode to work right with lsp. I work in vue with typescript and scss. I couldn't get typescript-mode working right (it wouldn't style (font lock?) the code without first entering a .ts file) in the ts section of the vue file, and I couldn't get syntax checking (lsp and then eslint) working right. I'm sure this is all my own fault, and because I don't understand emacs well enough. However, yesterday I gave up, and since I'd heard about emacs "polymode" (https://polymode.github.io/), I gave that a try. The following blog post was also helpful https://masteringemacs.org/article/polymode-multiple-major-modes-how-to-use-sql-python-in-one-buffer . This SE answer helped me with the regexps: https://superuser.com/a/1553876 .

The big advantage is that you define each section of the code by regexps for the start and end of the section, and you then define the major mode in each section freely. No major mode defining multiple major modes, just three separate major modes which you configure in whatever way you want.

The code that follows, from my init.el/.emacs, made that work for a vue file. This is terribly quick and dirty, but I still wanted to share it. Lsp and flycheck is only set up the way I want for the typescript section, so far.

(use-package polymode
  :mode ("\\.vue\\'" . poly-vue-mode) ; use-package allows us to set .vue files to a poly-vue-mode, which I will define below
  :config
  (define-hostmode poly-vue-hostmode :mode 'html-mode) ; The "host" mode is html-mode
  (define-innermode poly-typescript-vue-innermode ; The first inner mode is for typescript
    :mode 'typescript-mode
    :head-matcher "^[ \t]*<script.*>"
    :tail-matcher "^[ \t]*</script.*>"
    :head-mode 'host
    :tail-mode 'host)
  (define-innermode poly-scss-vue-innermode ; The second inner mode is for scss
    :mode 'scss-mode
    :head-matcher "^[ \t]*<style.*>"
    :tail-matcher "^[ \t]*</style.*>"    
    :head-mode 'host
    :tail-mode 'host    
    )
  (define-polymode poly-vue-mode ; Define a new polymode, with two inner modes
    :hostmode 'poly-vue-hostmode
    :innermodes '(poly-typescript-vue-innermode poly-scss-vue-innermode))
  )

It assumes the script section will always be typescript. It assumes the style section will always be scss. That's pretty lazy in comparison with what vue-mode does.

However, then I can separately define lsp, typescript-mode and scss-mode, with whatever settings I want. I get the vetur lsp server (I think that's because I've selected vetur in lsp-mode when I first opened a .vue file).

For example

This gets me typescript-mode with lsp and eslint checking.

(use-package typescript-mode
  :ensure-system-package (typescript-language-server . "npm i -g typescript-language-server")  ; This requires the (ensure-system-package) module in emacs
  :mode ("\\.ts\\'")
  :config
  ; Need to add the next checker only after lsp has been enabled
  (add-hook 'lsp-after-initialize-hook (lambda ()
                                         (flycheck-add-next-checker 'lsp 'javascript-eslint)))
  (setq typescript-indent-level 2))

This sets up lsp-mode, and enables lsp for typescript-mode, html-mode and scss mode. Note that I don't know if the :config section is reasonable or correct, but it's what I've got right now.

(use-package lsp-mode
  :hook
  (java-mode . lsp)
  (typescript-mode . lsp)
  (html-mode . lsp)
  (scss-mode . lsp)
  (lsp-mode . lsp-enable-which-key-integration)
  :init
  (setq lsp-keymap-prefix "C-c C-l l") ; Must bind this before package is loaded
  :config
  (setq lsp-completion-provider :capf)
  (setq lsp-enable-snippet t)
  (setq lsp-enable-indentation t)
  (setq lsp-semantic-highlighting t)
  (setq lsp-auto-configure t)
  ;; Settings for Vue.js (vetur) language server
  (setq lsp-vetur-use-workspace-dependencies t)
  (setq lsp-typescript-preferences-quote-style "single")
  )

It seems to just work!

@SjB
Copy link

SjB commented Jan 14, 2021

I've been using polymode also here's my configuration for my vue-mode

(use-package polymode
        :ensure t
        :defer t
        :hook (vue-mode . lsp-deferred)
        :mode ("\\.vue\\'" . vue-mode)
        :config


        (define-innermode poly-vue-template-innermode
          :mode 'html-mode
          :head-matcher "<[[:space:]]*template[[:space:]]*[[:space:]]*>"
          :tail-matcher "</[[:space:]]*template[[:space:]]*[[:space:]]*>"
          :head-mode 'host
          :tail-mode 'host)

        (define-innermode poly-vue-script-innermode
          :mode 'js-mode
          :head-matcher "<[[:space:]]*script[[:space:]]*[[:space:]]*>"
          :tail-matcher "</[[:space:]]*script[[:space:]]*[[:space:]]*>"
          :head-mode 'host
          :tail-mode 'host)

        (define-innermode poly-vue-typescript-innermode
          :mode 'typescript-mode
          :head-matcher "<[[:space:]]*script[[:space:]]*lang=[[:space:]]*[\"'][[:space:]]*ts[[:space:]]*[\"'][[:space:]]*>"
          :tail-matcher "</[[:space:]]*script[[:space:]]*[[:space:]]*>"
          :head-mode 'host
          :tail-mode 'host)

        (define-innermode poly-vue-javascript-innermode
          :mode 'js2-mode
          :head-matcher "<[[:space:]]*script[[:space:]]*lang=[[:space:]]*[\"'][[:space:]]*js[[:space:]]*[\"'][[:space:]]*>"
          :tail-matcher "</[[:space:]]*script[[:space:]]*[[:space:]]*>"
          :head-mode 'host
          :tail-mode 'host)

        (define-auto-innermode poly-vue-template-tag-lang-innermode
          :head-matcher "<[[:space:]]*template[[:space:]]*lang=[[:space:]]*[\"'][[:space:]]*[[:alpha:]]+[[:space:]]*[\"'][[:space:]]*>"
          :tail-matcher "</[[:space:]]*template[[:space:]]*[[:space:]]*>"
          :mode-matcher (cons  "<[[:space:]]*template[[:space:]]*lang=[[:space:]]*[\"'][[:space:]]*\\([[:alpha:]]+\\)[[:space:]]*[\"'][[:space:]]*>" 1)
          :head-mode 'host
          :tail-mode 'host)

        (define-auto-innermode poly-vue-script-tag-lang-innermode
          :head-matcher "<[[:space:]]*script[[:space:]]*lang=[[:space:]]*[\"'][[:space:]]*[[:alpha:]]+[[:space:]]*[\"'][[:space:]]*>"
          :tail-matcher "</[[:space:]]*script[[:space:]]*[[:space:]]*>"
          :mode-matcher (cons  "<[[:space:]]*script[[:space:]]*lang=[[:space:]]*[\"'][[:space:]]*\\([[:alpha:]]+\\)[[:space:]]*[\"'][[:space:]]*>" 1)
          :head-mode 'host
          :tail-mode 'host)

        (define-auto-innermode poly-vue-style-tag-lang-innermode
          :head-matcher "<[[:space:]]*style[[:space:]]*lang=[[:space:]]*[\"'][[:space:]]*[[:alpha:]]+[[:space:]]*[\"'][[:space:]]*>"
          :tail-matcher "</[[:space:]]*style[[:space:]]*[[:space:]]*>"
          :mode-matcher (cons  "<[[:space:]]*style[[:space:]]*lang=[[:space:]]*[\"'][[:space:]]*\\([[:alpha:]]+\\)[[:space:]]*[\"'][[:space:]]*>" 1)
          :head-mode 'host
          :tail-mode 'host)

        (define-innermode poly-vue-style-innermode
          :mode 'css-mode
          :head-matcher "<[[:space:]]*style[[:space:]]*[[:space:]]*>"
          :tail-matcher "</[[:space:]]*style[[:space:]]*[[:space:]]*>"
          :head-mode 'host
          :tail-mode 'host)

        (define-polymode vue-mode
          :hostmode 'poly-sgml-hostmode
          :innermodes '(
                        poly-vue-typescript-innermode
                        poly-vue-javascript-innermode
                        poly-vue-template-tag-lang-innermode
                        poly-vue-script-tag-lang-innermode
                        poly-vue-style-tag-lang-innermode
                        poly-vue-template-innermode
                        poly-vue-script-innermode
                        poly-vue-style-innermode
                        )))

@RKBK
Copy link
Author

RKBK commented Jan 14, 2021

@SjB I'll have to try your version! Looks much more refined.

@AlexDaniel
Copy link

Wow, this is just in time, thanks for sharing! I tried it and it seems to work! In terms of stability it is much better than vue-mode.

@AlexDaniel
Copy link

AlexDaniel commented Jan 16, 2021

Here I'll be keeping (and hopefully updating) a list of things that don't work yet in the solution provided by @SjB:

  • <style scoped> is not matched
  • clicking on suggestions from lsp almost always (?) results in the cursor jumping to a wrong position and no actions being done

@RKBK
Copy link
Author

RKBK commented Jan 22, 2021

@AlexDaniel Are you sure about number 2? When I tried it (admittedly using my configuration, but essentially it's the same thing as what @SjB posted):

  1. add an extra space at the end of a line (we've got some picky eslint rules)
  2. A red line appears, message "Trailing spaces not allowed"
  3. Click "Fix all auto-fixable problems"
  4. Problem is fixed, and the cursor then jumps to the same line that the suggestion from lsp was rendered at (this part is maybe a bit weird, but lsp works the same way in any buffer it looks like (e.g. a .ts file I opened))

Are you sure the auto-fix actually works on the lsp end for the suggestion you try to invoke? I think I've noticed that not all code actions work.

@AlexDaniel
Copy link

@RKBK not sure about number 2. I'm seeing some weird bugs but I think they're related to other packages.

@SjB
Copy link

SjB commented Feb 6, 2021

Here I'll be keeping (and hopefully updating) a list of things that don't work yet in the solution provided by @SjB:

  • <style scoped> is not matched
  • clicking on suggestions from lsp almost always (?) results in the cursor jumping to a wrong position and no actions being done

replace the poly-vue-style-tag-lang-innermode and poly-vue-style-innermode with this

(define-auto-innermode poly-vue-style-tag-lang-innermode
          :head-matcher "<[[:space:]]*style\\(?:scoped\\|[[:space:]]\\)*lang=[[:space:]]*[\"'][[:space:]]*[[:alpha:]]+[[:space:]]*[\"']*\\(?:scoped\\|[[:space:]]\\)*>"
          :tail-matcher "</[[:space:]]*style[[:space:]]*[[:space:]]*>"
          :mode-matcher (cons  "<[[:space:]]*style\\(?:scoped\\|[[:space:]]\\)*lang=[[:space:]]*[\"'][[:space:]]*\\([[:alpha:]]+\\)[[:space:]]*[\"']\\(?:scoped\\|[[:space:]]\\)*>" 1)
          :head-mode 'host
          :tail-mode 'host)

 (define-innermode poly-vue-style-innermode
          :mode 'css-mode
          :head-matcher "<[[:space:]]*style[[:space:]]*\\(?:scoped\\|[[:space:]]\\)*>"
          :tail-matcher "</[[:space:]]*style[[:space:]]*[[:space:]]*>"
          :head-mode 'host
          :tail-mode 'host)

@SjB
Copy link

SjB commented Feb 7, 2021

@AlexDaniel, @RKBK I'm creating a gist of my polymode configuration for vue-mode https://gist.github.com/SjB/07cdce0f1fba171704d93c2989077f4d

@tosiek88
Copy link

tosiek88 commented Feb 7, 2021

Hi it's working quite nice but i have some troubles with company:

Screenshot from 2021-02-07 21-47-49
Screenshot from 2021-02-07 21-48-27

When I am working with NestJS + Typescript looks like that
Screenshot from 2021-02-07 21-53-10

@Artawower
Copy link

I have a problem while typing in a typescript block. But this does not happen on all files.
image

I tried to add (add-to-list 'mmm-save-local-variables '(syntax-ppss-table buffer)) but it didn't solve my problem :(

@ctrl-alt-adrian
Copy link

For those that switched to polymode, how are you finding it for working with vue? I forked this repo to look at fixing some of the issues but if there is an alternative that exists then I'll try that out.

@RKBK
Copy link
Author

RKBK commented Oct 5, 2021

I liked the speed of the polymode, but at the time I wasn't able to debug and fix the sorts of issues that @Artawower mentioned. In the end, I gave up on the polymode and went to web-mode. That just works for vue files. I'm not sure if vue-mode does anything extra in excess of what web-mode allows for. The one thing I don't love about web-mode is that it feels a smidge slow sometimes (and recently I've had to refresh the connection to the vls server more often, but I'm not sure if that's because of web-mode or something else).

@Artawower
Copy link

The same situation. Now I am using web mode, there are also some problems like poor pug mode support and wrong indentation in sass mode, but this is better than other solutions

@ctrl-alt-adrian
Copy link

ctrl-alt-adrian commented Oct 5, 2021

Yeah. I've moved back to web-mode as well. I couldn't quite gel with polymode. So far, I just been playing around with this current config. Still rough around the edges.

(setq web-mode-content-type-alias
      '(("vue" . "\\.vue\\'")))
(use-package company-web
  :ensure t)
(add-hook 'web-mode-hook (lambda()
                           (cond ((equal web-mode-content-type "html")
                                  (my/web-html-setup)))
                           (cond ((equal web-mode-content-type "vue")
                                  (my/web-vue-setup)))
                           ))
)

;;
;; html
;;
(defun my/web-html-setup()
  "Setup for web-mode html files."
  (message "web-mode use html related setup")
  (flycheck-add-mode 'html-tidy 'web-mode)
  (flycheck-select-checker 'html-tidy)
  (add-to-list (make-local-variable 'company-backends)
               '(company-web-html company-files company-css company-capf company-dabbrev))
  ;; (add-hook 'before-save-hook #'sgml-pretty-print)
  )

;;
;; vue
;;
(defun my/web-vue-setup()
  "Setup for web-mode vue files."
  (message "web-mode use vue related setup")
  (setup-tide-mode)
  (flycheck-add-mode 'javascript-eslint 'web-mode)
  (my/use-eslint-from-node-modules)
  (flycheck-select-checker 'javascript-eslint)
  (add-to-list (make-local-variable 'company-backends)
               '(company-tide company-web-html compoany-files company-css))
  )

;;
;; eslint use local
;;
(defun my/use-eslint-from-node-modules()
  "Use local eslint from node_modules before global"
  (let* ((root (locate-dominating-file
                (or (buffer-file-name) default-directory)
                "node-modules"))
         (eslint (and root(
                           expeand-file-name "node_modules/eslint/bin/esling.js"
                                             root))))
    (when (and eslint (file-executable-p eslint))
      (setq-local flycheck-javascript-eslint-executable eslint)))
  (add-hook 'flycheck-mode-hook #'my/use-eslint-from-node-modules)
)

Not settings. Source here: https://github.com/jerryhsieh/Emacs-config/blob/master/init.el

I'm just seeing what sticks and possibly will adjust to my needs.

@noor-tg
Copy link

noor-tg commented Mar 24, 2022

@SjB do you use polymode with your config as replacement for vue-mode ?

@noor-tg
Copy link

noor-tg commented Mar 24, 2022

Yeah. I've moved back to web-mode as well. I couldn't quite gel with polymode. So far, I just been playing around with this current config. Still rough around the edges.

(setq web-mode-content-type-alias
      '(("vue" . "\\.vue\\'")))
(use-package company-web
  :ensure t)
(add-hook 'web-mode-hook (lambda()
                           (cond ((equal web-mode-content-type "html")
                                  (my/web-html-setup)))
                           (cond ((equal web-mode-content-type "vue")
                                  (my/web-vue-setup)))
                           ))
)

;;
;; html
;;
(defun my/web-html-setup()
  "Setup for web-mode html files."
  (message "web-mode use html related setup")
  (flycheck-add-mode 'html-tidy 'web-mode)
  (flycheck-select-checker 'html-tidy)
  (add-to-list (make-local-variable 'company-backends)
               '(company-web-html company-files company-css company-capf company-dabbrev))
  ;; (add-hook 'before-save-hook #'sgml-pretty-print)
  )

;;
;; vue
;;
(defun my/web-vue-setup()
  "Setup for web-mode vue files."
  (message "web-mode use vue related setup")
  (setup-tide-mode)
  (flycheck-add-mode 'javascript-eslint 'web-mode)
  (my/use-eslint-from-node-modules)
  (flycheck-select-checker 'javascript-eslint)
  (add-to-list (make-local-variable 'company-backends)
               '(company-tide company-web-html compoany-files company-css))
  )

;;
;; eslint use local
;;
(defun my/use-eslint-from-node-modules()
  "Use local eslint from node_modules before global"
  (let* ((root (locate-dominating-file
                (or (buffer-file-name) default-directory)
                "node-modules"))
         (eslint (and root(
                           expeand-file-name "node_modules/eslint/bin/esling.js"
                                             root))))
    (when (and eslint (file-executable-p eslint))
      (setq-local flycheck-javascript-eslint-executable eslint)))
  (add-hook 'flycheck-mode-hook #'my/use-eslint-from-node-modules)
)

Not settings. Source here: https://github.com/jerryhsieh/Emacs-config/blob/master/init.el

I'm just seeing what sticks and possibly will adjust to my needs.

does this config still work ?

  • does it support lsp ?
    • typescript lsp ?
    • volar lsp ?
  • what about eslint error check ?
  • company autocompletions ?
  • template block ?
    • error handle ?
    • components autocomplete and import ?

@AdrianDeveloper
if it is not working . is there any replacement to develop vue apps with emacs ?

@noor-tg
Copy link

noor-tg commented Mar 24, 2022

@AdrianDeveloper above reply

@ctrl-alt-adrian
Copy link

@AdrianDeveloper above reply

Sorry for the late reply. This worked ok, but in the end, I just moved back to neovim and that's where I'm at.

Good luck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants