282 lines
9.1 KiB
EmacsLisp
282 lines
9.1 KiB
EmacsLisp
;;; qml-mode.el --- Major mode for editing QT Declarative (QML) code.
|
|
|
|
;; Copyright (C) 2010 Wen-Chun Lin
|
|
;; Copyright (C) 2013-2016 Yen-Chin Lee
|
|
;; Copyleft (C) 2016 Ono Hiroko (@kuanyui)
|
|
|
|
;; Author: Yen-Chin Lee <coldnew.tw@gmail.com>
|
|
;; URL: https://github.com/coldnew/qml-mode
|
|
;; Package-Version: 0.4
|
|
;; Package-Revision: 6c5f33ba88ae
|
|
;; Keywords: qml, qt, qt declarative
|
|
|
|
;; This file is NOT part of GNU Emacs.
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation; either version 2, or (at your option)
|
|
;; any later version.
|
|
;;
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
;;
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program; if not, write to the Free Software
|
|
;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
;;; Commentary:
|
|
|
|
;;
|
|
;; qml-mode is major-mode for editing Qt Declarative (QML) code.
|
|
;;
|
|
|
|
;;; Installation:
|
|
|
|
;; If you have `melpa` and `emacs24` installed, simply type:
|
|
;;
|
|
;; M-x package-install qml-mode
|
|
;;
|
|
;; Add following lines to your init file:
|
|
;;
|
|
;; (autoload 'qml-mode "qml-mode" "Editing Qt Declarative." t)
|
|
;; (add-to-list 'auto-mode-alist '("\\.qml$" . qml-mode))
|
|
|
|
;;; ChangeLog
|
|
;;
|
|
;; 0.4
|
|
;;
|
|
;; * Add QML component id highlighter
|
|
;; * Add basic types provided by QML modules as property keyword
|
|
;;
|
|
;; 0.3
|
|
;;
|
|
;; * rewrite based on js-mode.
|
|
;;
|
|
;; 0.2
|
|
;;
|
|
;; * rewrite based on generic-mode.
|
|
;;
|
|
;; 0.1
|
|
;;
|
|
;; * first version fork from cataska/qml-mode.
|
|
|
|
;;; Code:
|
|
(require 'js) ; based on js-mode
|
|
(require 'font-lock)
|
|
|
|
;;; User Customization
|
|
|
|
(defgroup qml nil
|
|
"Customization variables for QML mode."
|
|
:tag "QML"
|
|
:group 'languages)
|
|
|
|
;;; Functions
|
|
|
|
(defun qml--list-to-string (list)
|
|
"Combine a list to string."
|
|
(concat "\\(" (mapconcat 'identity list "\\|") "\\)"))
|
|
|
|
(defun qml-beginning-of-defun ()
|
|
"Value of `beginning-of-defun-function' for `qml-mode'."
|
|
(interactive)
|
|
(re-search-backward "\{"))
|
|
|
|
(defun qml-end-of-defun ()
|
|
"Value of `end-of-defun-function' for `qml-mode'."
|
|
(interactive)
|
|
(re-search-forward "\}"))
|
|
|
|
;;; KeyMap
|
|
|
|
(defvar qml-mode-map
|
|
(let ((keymap (make-sparse-keymap)))
|
|
(define-key keymap (kbd "C-M-a") 'qml-beginning-of-defun)
|
|
(define-key keymap (kbd "C-M-e") 'qml-end-of-defun)
|
|
keymap)
|
|
"Keymap for `qml-mode'.")
|
|
|
|
;;; Syntax table and parsing
|
|
|
|
(defconst qml--class-re
|
|
(js--regexp-opt-symbol
|
|
'("State" "PropertyChanges" "StateGroup" "ParentChange"
|
|
"StateChangeScript" "AnchorChanges" "PropertyAnimation" "NumberAnimation"
|
|
"ColorAnimation" "RotationAnimation" "SequentialAnimation" "ParallelAnimation"
|
|
"PauseAnimation" "ParentAnimation" "AnchorAnimation" "SmoothedAnimation"
|
|
"PropertyAction" "ScriptAction" "Transition" "SpringFollow"
|
|
"Behavior" "Binding" "ListModel" "ListElement"
|
|
"VisualItemModel" "VisualDataModel" "Package" "XmlListModel"
|
|
"XmlRole" "Connections" "Component" "Timer"
|
|
"QtObject" "WorkerScript" "Item" "Rectangle"
|
|
"Image" "BorderImage" "Text" "TextInput"
|
|
"TextEdit" "MouseArea" "FocusScope" "Flickable"
|
|
"Flipable" "GestureArea" "Loader" "Repeater"
|
|
"SystemPalette" "LayoutItem" "Scale" "Rotation"
|
|
"Translate" "ViewsPositionersMediaEffects" "ListView" "GridView"
|
|
"PathView" "Path" "PathLine" "PathQuad"
|
|
"PathCubic" "PathAttribute" "PathPercent" "WebView"
|
|
"Column" "Row" "Grid" "Flow"
|
|
"SoundEffect" "Audio" "Video" "Particles"
|
|
"ParticleMotionLinear" "ParticleMotionGravity" "ParticleMotionWander"))
|
|
"Regexp matching any QML class")
|
|
|
|
(defconst qml--type-re
|
|
(js--regexp-opt-symbol
|
|
'("import" "signal" "Qt" "parent"))
|
|
"Regular expression matching any predefined type in QML.")
|
|
|
|
(defconst qml--property
|
|
'("bool" "double" "real" "int"
|
|
"string" "url" "color" "date"
|
|
"variant" "alias"
|
|
"font" "matrix4x4" "point" "quaternion" "rect" "size" "vector2d" "vector3d" "vector4d"
|
|
))
|
|
|
|
|
|
(defconst qml--property-re
|
|
(concat "\\(property[ \t]+" (qml--list-to-string qml--property) "\\)+[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)"))
|
|
|
|
(defconst qml--constants-re
|
|
(js--regexp-opt-symbol
|
|
'("NoButton" "LeftButton" "RightButton" "MidButton"
|
|
"MiddleButton"
|
|
"Horizontal" "Vertical"
|
|
"AlignLeft" "AlignRight" "AlignHCenter" "AlignTop"
|
|
"AlignBottom" "AlignVCenter" "AlignCenter"
|
|
"Easing" "Linear" "InQuad" "OutQuad"
|
|
"InOutQuad" "OutInQuad" "InCubic" "OutCubic"
|
|
"InOutCubic" "OutInCubic" "InQuart" "OutQuart"
|
|
"InOutQuart" "OutInQuart" "InQuint" "InQuint"
|
|
"OutQuint" "InOutQuint" "OutInQuint" "InSine"
|
|
"OutSine" "InExpo" "OutExpo" "InOutExpo"
|
|
"OutInExpo" "InCirc" "OutCirc" "InOutCirc"
|
|
"OutInCirc" "InElastic" "OutElastic" "InOutElastic"
|
|
"OutInElastic" "InBack" "OutBack" "InOutBack"
|
|
"OutInBack" "InBounce" "OutBounce" "InOutBounce"
|
|
"OutInBounce")))
|
|
|
|
(defconst qml--font-lock-keywords-1
|
|
`( ;; Keywords
|
|
(,qml--class-re (0 'font-lock-keyword-face))
|
|
(,js--keyword-re (0 'font-lock-keyword-face))
|
|
;; Types
|
|
(,qml--type-re (1 'font-lock-type-face))
|
|
(,js--basic-type-re (1 'font-lock-type-face))
|
|
;; Constant
|
|
(,qml--constants-re (0 font-lock-constant-face))
|
|
(,js--constant-re (0 font-lock-constant-face))
|
|
("\\<id[ \t]*:[ \t]*\\([a-zA-Z0-9_]+\\)" (1 'font-lock-preprocessor-face))
|
|
("\\([+-]?\\<[0-9]*\\.?[0-9]+[xX]?[0-9a-fA-F]*\\)" . font-lock-constant-face)
|
|
("\\([a-zA-Z_\\.]+[a-zA-Z0-9_]*\\)[ \t]*:" (1 font-lock-variable-name-face))
|
|
;; builtin
|
|
("\\([a-zA-Z0-9]+\\)[ \t]*{" (1 'font-lock-builtin-face))
|
|
;; Function
|
|
("\\(function\\|signal\\)\\{1\\}[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)" (2 'font-lock-function-name-face))
|
|
;; string
|
|
("\\('[[:alpha:]]*'\\)" (1 'font-lock-string-face)))
|
|
"Keywords to highlight in `qml-mode'.")
|
|
|
|
(defconst qml--font-lock-keywords-2
|
|
`(;; This goes before keywords-1 so it gets used preferentially
|
|
;; instead of the keywords in keywords-1. Don't use override
|
|
;; because that will override syntactic fontification too, which
|
|
;; will fontify commented-out directives as if they weren't
|
|
;; commented out.
|
|
(,qml--property-re (1 'font-lock-type-face)
|
|
(3 'font-lock-variable-name-face))
|
|
|
|
,@qml--font-lock-keywords-1))
|
|
|
|
(defconst qml--font-lock-keywords
|
|
'(qml--font-lock-keywords-2
|
|
qml--font-lock-keywords-1
|
|
qml--font-lock-keywords-2))
|
|
|
|
(defvar-local qml--id-list '())
|
|
(defvar-local qml-refresh-timer nil "Buffer-local timer.")
|
|
|
|
;; ======================================================
|
|
;; Highlight component id
|
|
;; ======================================================
|
|
|
|
(defun qml--search-all-id-in-current-file ()
|
|
"Get all compnents' id defined in current QML file."
|
|
(let (id-list)
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(while (re-search-forward "\\<id[ \t]*:[ \t]*\\([a-zA-Z0-9_]+\\)" nil :no-error)
|
|
(cl-pushnew (match-string-no-properties 1) id-list :test #'string-equal)))
|
|
id-list))
|
|
|
|
(defun qml--gen-font-lock-keywords (string-list face)
|
|
(list
|
|
(list (js--regexp-opt-symbol string-list) 0 face 'prepend)))
|
|
|
|
(defun qml--highlight-all-id-in-current-file ()
|
|
(interactive)
|
|
(let ((old-id-list qml--id-list)
|
|
(new-id-list (qml--search-all-id-in-current-file)))
|
|
(when (not (equal old-id-list new-id-list))
|
|
(if old-id-list
|
|
(font-lock-remove-keywords nil (qml--gen-font-lock-keywords old-id-list 'font-lock-preprocessor-face)))
|
|
(setq qml--id-list new-id-list)
|
|
(font-lock-add-keywords nil (qml--gen-font-lock-keywords new-id-list 'font-lock-preprocessor-face) 'append)
|
|
(qml--flush))
|
|
))
|
|
|
|
(defun qml--flush ()
|
|
(if (fboundp 'font-lock-flush)
|
|
(font-lock-flush)
|
|
;; Emacs < 25
|
|
(with-no-warnings
|
|
(font-lock-fontify-buffer))))
|
|
|
|
;; ======================================================
|
|
;; Idle Timer
|
|
;; ======================================================
|
|
|
|
(defun qml-timer-handler (buffer)
|
|
(when (buffer-live-p buffer)
|
|
(with-current-buffer buffer
|
|
(qml--highlight-all-id-in-current-file)
|
|
)))
|
|
|
|
(add-hook 'qml-mode-hook
|
|
(lambda ()
|
|
(setq qml-refresh-timer
|
|
(run-with-idle-timer 1.0 t 'qml-timer-handler (current-buffer)))))
|
|
|
|
(add-hook 'kill-buffer-hook
|
|
(lambda ()
|
|
(when (timerp qml-refresh-timer)
|
|
(cancel-timer qml-refresh-timer))))
|
|
|
|
|
|
;;;###autoload
|
|
(define-derived-mode qml-mode js-mode "QML"
|
|
"Major mode for editing QML.
|
|
|
|
\\{qml-mode-map}"
|
|
:group 'qml
|
|
|
|
(setq-local font-lock-defaults (list qml--font-lock-keywords))
|
|
(setq-local beginning-of-defun-function 'qml-beginning-of-defun)
|
|
(setq-local end-of-defun-function 'qml-end-of-defun)
|
|
|
|
;; C-style comment /* ... */
|
|
(setq-local comment-start "/*")
|
|
(setq-local comment-start-skip "/[*/]+[ \t]*")
|
|
(setq-local comment-end "*/")
|
|
(setq-local comment-end-skip "[ \t]*\\(?:\n\\|\\*+/\\)")
|
|
(setq-local comment-continue " *"))
|
|
|
|
;;;###autoload
|
|
(add-to-list 'auto-mode-alist '("\\.qml$" . qml-mode))
|
|
|
|
|
|
(provide 'qml-mode)
|
|
|
|
;;; qml-mode.el ends here
|