all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: "Simen Heggestøyl" <simenheg@gmail.com>
To: 24389@debbugs.gnu.org
Cc: Stefan Monnier <monnier@iro.umontreal.ca>,
	Dmitry Gutov <dgutov@yandex.ru>
Subject: bug#24389: [PATCH] Support completion of classes and IDs in CSS mode
Date: Wed, 07 Sep 2016 19:40:25 +0200	[thread overview]
Message-ID: <1473270025.21515.0@smtp.gmail.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 580 bytes --]

Hello.

I've got a seemingly working version of completion of HTML class names
and IDs in CSS mode buffers.

The idea is that CSS mode asks other open buffers if they are able to
produce completion candidates for HTML classes and IDs by checking
whether the functions `html-class-extractor-function' or
`html-id-extractor-function' are bound. I've included implementations
for these in HTML mode using libxml. My hope is that other modes, such
as Web mode [1], will be be able to implement their own extractor
functions.

What do you think?

-- Simen


[1] http://web-mode.org/



[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Support-completion-of-classes-and-IDs-in-CSS-mode.patch --]
[-- Type: text/x-patch, Size: 6458 bytes --]

From 45503ade03b277e2f66f2259b0436aa35a162bf2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Simen=20Heggest=C3=B8yl?= <simenheg@gmail.com>
Date: Sat, 25 Jun 2016 15:12:09 +0200
Subject: [PATCH] Support completion of classes and IDs in CSS mode

* lisp/textmodes/css-mode.el (css--complete-html-tag): New function
for completing HTML tags, which was previously the responsibility of
`css--complete-selector'.
(css--foreign-completion-cache): New variable holding a cache for
completions provided by other buffers.
(css--foreign-completions): New function for retrieving completions
from other buffers.
(css--complete-selector): Support completing HTML IDs and classes from
other buffers in addition to completing HTML tags.

* lisp/textmodes/sgml-mode.el (html-current-buffer-classes): New
function returning a list of class names used in the current buffer.
(html-current-buffer-ids): New function returning a list of IDs used
in the current buffer.
(html-class-extractor-function): New variable holding the function to
use for class name extraction.
(html-id-extractor-function): New variable holding the function to use
for ID extraction.
(html-mode): Set `html-class-extractor-function' and
`html-id-extractor-function' to `html-current-buffer-classes' and
`html-current-buffer-ids' respectively.
---
 lisp/textmodes/css-mode.el  | 56 +++++++++++++++++++++++++++++++++++++++------
 lisp/textmodes/sgml-mode.el | 29 +++++++++++++++++++++++
 2 files changed, 78 insertions(+), 7 deletions(-)

diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 4d8170e..20f3ff2 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -30,7 +30,6 @@
 ;; - electric ; and }
 ;; - filling code with auto-fill-mode
 ;; - fix font-lock errors with multi-line selectors
-;; - support completion of user-defined classes names and IDs
 
 ;;; Code:
 
@@ -864,16 +863,59 @@ css--nested-selectors-allowed
   "Non-nil if nested selectors are allowed in the current mode.")
 (make-variable-buffer-local 'css--nested-selectors-allowed)
 
-;; TODO: Currently only supports completion of HTML tags.  By looking
-;; at open HTML mode buffers we should be able to provide completion
-;; of user-defined classes and IDs too.
+(defun css--complete-html-tag ()
+  "Complete HTML tag at point."
+  (save-excursion
+    (let ((end (point)))
+      (skip-chars-backward "-[:alnum:]")
+      (list (point) end css--html-tags))))
+
+(defvar css--foreign-completion-cache
+  (list 'html-class-extractor-function (make-hash-table :test 'equal)
+        'html-id-extractor-function (make-hash-table :test 'equal))
+  "Cache of completions provided by other buffers.
+This is a property list where each property is the name of an
+extractor function and the associated value is a hash table
+serving as a cache for that function.")
+
+(defun css--foreign-completions (extractor)
+  "Return a list of completions provided by other buffers.
+EXTRACTOR should be the name of a function that may be defined in
+one or more buffers.  In each of the buffers where EXTRACTOR is
+defined, EXTRACTOR is called and the results are accumulated into
+a list."
+  (seq-uniq
+   (seq-mapcat
+    (lambda (buf)
+      (with-current-buffer buf
+        (when (boundp extractor)
+          (let ((cache
+                 (plist-get css--foreign-completion-cache extractor)))
+            (if cache
+                (let ((hash (buffer-hash buf)))
+                  (or (gethash hash cache)
+                      (puthash hash (funcall (symbol-value extractor))
+                               cache)))
+              (funcall (symbol-value extractor)))))))
+    (buffer-list))))
+
 (defun css--complete-selector ()
   "Complete part of a CSS selector at point."
   (when (or (= (nth 0 (syntax-ppss)) 0) css--nested-selectors-allowed)
-    (save-excursion
-      (let ((end (point)))
+    (let ((end (point)))
+      (save-excursion
         (skip-chars-backward "-[:alnum:]")
-        (list (point) end css--html-tags)))))
+        (let ((start-char (char-before)))
+          (list
+           (point) end
+           (completion-table-dynamic
+            (lambda (_)
+              (cond
+               ((eq start-char ?.) (css--foreign-completions
+                                    'html-class-extractor-function))
+               ((eq start-char ?#) (css--foreign-completions
+                                    'html-id-extractor-function))
+               (t css--html-tags))))))))))
 
 (defun css-completion-at-point ()
   "Complete current symbol at point.
diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el
index 990c09b..5e943de 100644
--- a/lisp/textmodes/sgml-mode.el
+++ b/lisp/textmodes/sgml-mode.el
@@ -32,6 +32,9 @@
 
 ;;; Code:
 
+(require 'dom)
+(require 'seq)
+(require 'subr-x)
 (eval-when-compile
   (require 'skeleton)
   (require 'cl-lib))
@@ -2168,6 +2171,27 @@ html-current-defun-name
 	 nil t)
 	(match-string-no-properties 1))))
 
+(defun html-current-buffer-classes ()
+  "Return a list of class names used in the current buffer."
+  (let ((dom (libxml-parse-html-region (point-min) (point-max))))
+    (seq-mapcat
+     (lambda (el)
+       (when-let (class-list (cdr (assq 'class (dom-attributes el))))
+         (split-string class-list)))
+     (dom-by-class dom ""))))
+
+(defun html-current-buffer-ids ()
+  "Return a list of IDs used in the current buffer."
+  (let ((dom (libxml-parse-html-region (point-min) (point-max))))
+    (seq-mapcat
+     (lambda (el)
+       (when-let (id-list (cdr (assq 'id (dom-attributes el))))
+         (split-string id-list)))
+     (dom-by-id dom ""))))
+
+(defvar html-class-extractor-function)
+(defvar html-id-extractor-function)
+
 \f
 ;;;###autoload
 (define-derived-mode html-mode sgml-mode '(sgml-xml-mode "XHTML" "HTML")
@@ -2218,6 +2242,11 @@ html-mode
   (setq-local add-log-current-defun-function #'html-current-defun-name)
   (setq-local sentence-end-base "[.?!][]\"'”)}]*\\(<[^>]*>\\)*")
 
+  (when (fboundp 'libxml-parse-html-region)
+    (setq-local html-class-extractor-function
+                #'html-current-buffer-classes)
+    (setq-local html-id-extractor-function #'html-current-buffer-ids))
+
   (setq imenu-create-index-function 'html-imenu-index)
 
   (setq-local sgml-empty-tags
-- 
2.9.3


             reply	other threads:[~2016-09-07 17:40 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-09-07 17:40 Simen Heggestøyl [this message]
2016-09-07 20:01 ` bug#24389: [PATCH] Support completion of classes and IDs in CSS mode Stefan Monnier
2016-09-10 12:13   ` Simen Heggestøyl
2016-09-10 19:55     ` Stefan Monnier
2016-09-17  7:10       ` Simen Heggestøyl
2016-09-17 12:35         ` Stefan Monnier
2016-09-24 11:58           ` Simen Heggestøyl
2016-09-24 15:00         ` Nicolas Petton

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1473270025.21515.0@smtp.gmail.com \
    --to=simenheg@gmail.com \
    --cc=24389@debbugs.gnu.org \
    --cc=dgutov@yandex.ru \
    --cc=monnier@iro.umontreal.ca \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.