From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Helmut Eller Newsgroups: gmane.emacs.devel Subject: Re: Entering Unicode characters Date: Sat, 06 Feb 2016 12:56:14 +0100 Message-ID: References: <83twmkkv16.fsf@gnu.org> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: ger.gmane.org 1454759807 1691 80.91.229.3 (6 Feb 2016 11:56:47 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Sat, 6 Feb 2016 11:56:47 +0000 (UTC) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sat Feb 06 12:56:40 2016 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1aS1Tj-0006hI-CC for ged-emacs-devel@m.gmane.org; Sat, 06 Feb 2016 12:56:39 +0100 Original-Received: from localhost ([::1]:53187 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aS1Ti-0001o7-TR for ged-emacs-devel@m.gmane.org; Sat, 06 Feb 2016 06:56:38 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:38642) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aS1Td-0001ky-25 for emacs-devel@gnu.org; Sat, 06 Feb 2016 06:56:34 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aS1TY-0001vl-QX for emacs-devel@gnu.org; Sat, 06 Feb 2016 06:56:33 -0500 Original-Received: from plane.gmane.org ([80.91.229.3]:59176) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aS1TY-0001uW-9a for emacs-devel@gnu.org; Sat, 06 Feb 2016 06:56:28 -0500 Original-Received: from list by plane.gmane.org with local (Exim 4.69) (envelope-from ) id 1aS1TV-0006OI-JS for emacs-devel@gnu.org; Sat, 06 Feb 2016 12:56:26 +0100 Original-Received: from 212.46.169.192 ([212.46.169.192]) by main.gmane.org with esmtp (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Sat, 06 Feb 2016 12:56:25 +0100 Original-Received: from eller.helmut by 212.46.169.192 with local (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Sat, 06 Feb 2016 12:56:25 +0100 X-Injected-Via-Gmane: http://gmane.org/ Original-Lines: 801 Original-X-Complaints-To: usenet@ger.gmane.org X-Gmane-NNTP-Posting-Host: 212.46.169.192 User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.50 (gnu/linux) Cancel-Lock: sha1:aYLVhaqXktjlmCa2FGylT6gO0VQ= X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 80.91.229.3 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:199407 Archived-At: --=-=-= Content-Type: text/plain On Tue, Jan 26 2016, Richard Stallman wrote: > Would someone please implement this? Here is my shot at this. Load the attached file and start the browser with "M-x ubb-browse". That displays the characters of the "Basic Latin" block. From there it's probably the easiest to use the menu to explore the available commands. I defined a sets of characters for some languages by looking at Wikipedia. This would obviously need more work by people who are familiar with those languages. The file is also available at: https://github.com/ellerh/ubb Helmut --=-=-= Content-Type: application/emacs-lisp; charset=utf-8 Content-Disposition: attachment; filename=ubb.el Content-Transfer-Encoding: quoted-printable ;;; ubb.el --- Unicode block browser -*-coding:utf-8; lexical-binding:= t-*- ;; Copyright (C) 2016 Free Software Foundation, Inc. ;; GNU Emacs 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 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs 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 GNU Emacs. If not, see . ;;; Commentary: ;; This is a mode for browsing the characters in Unicode Blocks or ;; other collections of characters, like Emacs' "charsets". ;; ;; Start the browser with "M-x ubb-browse". That displays the ;; characters of the "Basic Latin" block. From there it's probably ;; the easiest to use the menu to explore the available commands. ;; ;; The browser displays the current "set" of the current "group". A ;; set is a collection of codepoints, e.g. the Unicode Block "Box ;; Drawing" is such a set. Groups are collections of sets, e.g. all ;; Unicode Blocks form a group. ;; ;; Displaying large sets can be slow. Don't hesitate to press C-g if ;; it takes too long. ;;; Code (require 'cl-lib) ;;; Some type definitions ;; A ubb--set represents a set of codepoints. (cl-defstruct (ubb--set (:constructor ubb--make-set% (name ranges)) (:constructor nil) (:predicate nil) (:copier nil)) ;; NAME is a string for display purposes. (name "" :type string :read-only t) ;; RANGES is a list of the form ((START . END) ...) ;; START and END denote a range of codepoints. ;; START is inclusive; END is exclusive. (ranges () :type list :read-only t)) (defun ubb--check-range (x) (cl-assert (consp x)) (let ((start (car x)) (end (cdr x))) (cl-assert (integerp start)) (cl-assert (integerp end)) (cl-assert (<=3D 0 start)) (cl-assert (< start end)))) (defun ubb--sort-ranges (ranges) (sort (copy-sequence ranges) (lambda (r1 r2) (< (car r1) (car r2))))) (defun ubb--ranges-to-set (ranges) (cond ((null ranges) '()) ((null (cdr ranges)) ranges) (t (let ((sorted (ubb--sort-ranges ranges))) (nreverse (cl-reduce (lambda (left+m right) (cl-destructuring-bind ((s1 &rest e1) &rest m) left+m (cl-destructuring-bind (s2 &rest e2) right (cl-assert (<=3D s1 s2)) (cond ((<=3D e1 s2) ; no overlap (cons right left+m)) (t (cons (cons (min s1 s2) (max e1 e2)) m)))))) (cdr sorted) :initial-value (list (car sorted)))))))) (defun ubb--make-set (name ranges) (cl-check-type name string) (cl-check-type ranges list) (mapc #'ubb--check-range ranges) (let* ((sorted (ubb--ranges-to-set ranges))) (cl-loop for ((_ . end1) (start2 . _)) on sorted while start2 do (cl-assert (<=3D end1 start2))) (ubb--make-set% name sorted))) (defun ubb--set-size (set) (cl-loop for (start . end) in (ubb--set-ranges set) sum (- end start))) ;; Call FUN for each codepoint in SET. FUN receives two arguments: ;; and "index" and the codepoint. (defun ubb--set-foreachi (fun set) (let ((i 0)) (cl-loop for (start . end) in (ubb--set-ranges set) do (cl-loop for codepoint from start below end do (funcall fun i codepoint) (cl-incf i))))) (defun ubb--set-member? (set codepoint) (cl-loop for (start . end) in (ubb--set-ranges set) thereis (and (<=3D start codepoint) (< codepoint end)))) ;; Return a list of ranges corresponding to set of codepoints in the ;; string STRING. (defun ubb--string-ranges (string) (let* ((sorted (cl-sort (copy-sequence string) #'<)) (ranges '()) (start nil) (end nil)) (cl-loop for c across sorted do (cond ((not start) (setq start c) (setq end (1+ c))) ((=3D c (1- end))) ((=3D c end) (setq end (1+ c))) (t (push (cons start end) ranges) (setq start c) (setq end (1+ c))))) (when start (push (cons start end) ranges)) (reverse ranges))) ;; A ubb--group is used to represent a set of ubb--sets. (cl-defstruct (ubb--group (:constructor ubb--make-group) (:copier nil) (:predicate nil)) ;; NAME is a string for display purposes (name "" :type string :read-only t) ;; SETS%% is either a function (with no arguments) that should return a ;; sequence of ubb--sets that belong to this group, or the cached result ;; of calling the function. (sets%% (error "Arg missing") :type (or function sequence)) ;; HEADER is a function which receives a set as argument and ;; should the value for `header-line-format'. (header (error "Arg missing") :type function :read-only t)) (defun ubb--group-sets (group) (let ((sets%% (ubb--group-sets%% group))) (cl-etypecase sets%% (sequence sets%%) (function (setf (ubb--group-sets%% group) (funcall sets%%)))))) ;;; Unicode blocks ;; Local copy. (defvar ubb--blocks-file-name (expand-file-name "../admin/unidata/Blocks.txt" data-directory)) ;; Fallback if no local copy. (defvar ubb--blocks-url "http://www.unicode.org/Public/UNIDATA/Blocks.txt") ;; Parse blocks in the format used by the Unicode Character Database. (defun ubb--parse-blocks () (goto-char (point-min)) (let ((result '())) (while (re-search-forward "^\\([0-9A-F]+\\)\\.\\.\\([0-9A-F]+\\);[ ]*\\([^ ].*\\)$" nil t) (let* ((start (string-to-number (match-string 1) 16)) (end (1+ (string-to-number (match-string 2) 16))) (name (match-string 3))) (push (ubb--make-set name (list (cons start end))) result))) (let ((r (cl-coerce (nreverse result) 'vector))) (cl-assert (let ((s (elt r 0))) (and (equal (ubb--set-name s) "Basic Latin") (equal (ubb--set-ranges s) '((0 . #x80)))))) r))) ;; Load block information from a file or if the file isn't present ;; download it from unicode.org. (defun ubb--load-blocks () (with-temp-buffer (cond ((file-exists-p ubb--blocks-file-name) (let ((coding-system-for-read 'binary)) (insert-file-contents-literally ubb--blocks-file-name))) (t (insert (with-current-buffer (url-retrieve-synchronously ubb--blocks-url) (re-search-forward "\n\n") (buffer-substring (point) (point-max)))))) (ubb--parse-blocks))) (defun ubb--all-blocks () "Return a sequence of all blocks." (ubb--load-blocks)) (defun ubb--find-block-by-codepoint (codepoint) (cl-find-if (lambda (block) (ubb--set-member? block codepoint)) (ubb--all-blocks))) (defun ubb--unicode-block-header (set) (cl-destructuring-bind ((start &rest end)) (ubb--set-ranges set) (format "Block: %s %04X..%04X" (ubb--set-name set) start end))) (defvar ubb--unicode-blocks-group (ubb--make-group :name "Unicode blocks" :sets%% #'ubb--all-blocks :header #'ubb--unicode-block-header)) ;;; Charsets ;; NOTE: `map-charset-chars' can call the function with overlapping ;; ranges. Also the cons cell for the range argument is updated so ;; it's a good idea to copy the contents instead of using the cons ;; cell. (defun ubb--charset-to-set (charset) (let ((ranges '())) (map-charset-chars (lambda (from+to _) (cl-destructuring-bind (from &rest to) from+to (cl-assert (characterp from)) (cl-assert (characterp to)) (push (cons from (1+ to)) ranges))) charset) (ubb--make-set (or (get-charset-property charset :long-name) (get-charset-property charset :short-name) (format "%s" charset)) ranges))) (defun ubb--charsets-without-aliases () (reverse ; ascii first, please (cl-remove-duplicates charset-list :key #'charset-plist))) (defun ubb--all-charsets () (mapcar #'ubb--charset-to-set (ubb--charsets-without-aliases))) (defun ubb--charset-header (set) (format "Charset: %s" (ubb--set-name set))) (defvar ubb--charsets-group (ubb--make-group :name "Charsets" :sets%% #'ubb--all-charsets :header #'ubb--charset-header)) ;;; Scripts (defun ubb--all-scripts () (let ((script2ranges (make-hash-table)) (sets '())) (map-char-table (lambda (key script) (setf (gethash script script2ranges) (cons (cl-etypecase key (character (cons key (1+ key))) (cons (cons (car key) (1+ (cdr key))))) (gethash script script2ranges)))) char-script-table) (maphash (lambda (script ranges) (push (ubb--make-set (symbol-name script) ranges) sets)) script2ranges) (cl-sort sets #'string< :key #'ubb--set-name))) (defvar ubb--scripts-group (ubb--make-group :name "Scripts" :sets%% #'ubb--all-scripts :header (lambda (set) (format "Script: %s" (ubb--set-name set))))) ;;; Unicode categories (defun ubb--all-unicode-categories () (let ((cat2ranges (make-hash-table)) (sets '())) (map-char-table (lambda (key cat) (setf (gethash cat cat2ranges) (cons (cl-etypecase key (character (cons key (1+ key))) (cons (cons (car key) (1+ (cdr key))))) (gethash cat cat2ranges)))) unicode-category-table) (maphash (lambda (cat ranges) (let* ((desc (char-code-property-description 'general-category cat)) (name (format "%s (%s)" cat desc))) (push (ubb--make-set name ranges) sets))) cat2ranges) (cl-sort sets #'string< :key #'ubb--set-name))) (defvar ubb--unicode-categories-group (ubb--make-group :name "Unicode categories" :sets%% #'ubb--all-unicode-categories :header (lambda (set) (format "Unicode general category: %s" (ubb--set-name set))))) ;;; Languages (defun ubb--all-languages () (list (ubb--make-set "English" (ubb--string-ranges "=E2=80=9C=E2=80=9D=E2= =80=98=E2=80=99")) (ubb--make-set "French" (ubb--string-ranges "\ =C3=A9=C3=A0=C3=A8=C3=B9=C3=A2=C3=AA=C3=AE=C3=B4=C3=BB=C3=AB=C3=AF=C3=BC=C3= =BF=C3=A7=C5=93=C3=A6\ =C3=89=C3=80=C3=88=C3=99=C3=82=C3=8A=C3=8E=C3=94=C3=9B=C3=8B=C3=8F=C3=9C=C5= =B8=C3=87=C5=92=C3=86\ =C2=AB=C2=BB=E2=80=B9=E2=80=BA=E2=80=9C=E2=80=9D=E2=80=98=E2=80=99=E2=82=AC= ")) (ubb--make-set "German" (ubb--string-ranges "=C3=A4=C3=B6=C3=BC=C3=9F=C3= =84=C3=96=C3=9C=E2=80=9E=E2=80=9C=E2=80=9A=E2=80=98=E2=80=99=C2=BB=C2=AB=E2= =80=BA=E2=80=B9=E2=82=AC")) (ubb--make-set "Italian" (ubb--string-ranges "\ =C3=A0=C3=A8=C3=AC=C3=B2=C3=B9=C3=A9=C3=B3=C3=AE\ =C3=80=C3=88=C3=8C=C3=92=C3=99=C3=89=C3=93=C3=8E\ =E2=80=9C=E2=80=9D=E2=80=98=E2=80=99=C2=AB=C2=BB=E2=80=B9=E2=80=BA=E2=82=AC= ")) (ubb--make-set "Spanish" (ubb--string-ranges "\ =C3=B1=C3=A1=C3=A9=C3=AD=C3=B3=C3=BA=C3=BC\ =C3=91=C3=81=C3=89=C3=8D=C3=93=C3=9A=C3=9C\ =E2=80=9C=E2=80=9D=E2=80=98=E2=80=99=C2=AB=C2=BB=C2=BF=C2=A1")) (ubb--make-set "Turkish" (ubb--string-ranges "\ =C3=A7=C5=9F=C4=9F=C4=B1=C3=B6=C3=BC\ =C3=87=C5=9E=C4=9E=C4=B0=C3=96=C3=9C\ =E2=80=9C=E2=80=9D=E2=80=98=E2=80=99=C2=AB=C2=BB=E2=80=B9=E2=80=BA")) (ubb--make-set "APL" (append (ubb--string-ranges "\ ?=E2=8C=88=E2=8C=8A=E2=8D=B4=E2=88=BC=E2=88=A3=E2=8D=B3=E2=8B=86=E2=88=92+= =C3=97=C3=B7,=E2=8C=B9=E2=97=8B=E2=8D=9F=E2=8C=BD=E2=8A=96=E2=8D=8B=E2=8D= =92=E2=8D=8E=E2=8D=95=E2=8D=89!=E2=88=92=C3=97=C3=B7=E2=8B=86=E2=97=8B?=E2= =88=88=E2=8C=88=E2=8C=8A=E2=8D=B4=E2=86=91=E2=86=93=E2=8A=A5=E2=8A=A4=E2=88= =A3,\/=E2=8D=B3=E2=8C=B9=E2=8C=BD=E2=8A=96=E2=8D=9F=E2=8D=95=E2=8D=89!=C2= =A8<=E2=89=A4=3D=E2=89=A5>=E2=89=A0=E2=88=A8=E2=88=A7=E2=8D=B1=E2=8D=B2/=E2= =8C=BF\=E2=8D=80.=E2=88=98.") `((,?=E2=8C=B6 . ,(1+ ?=E2=8D=BA))))))) (defvar ubb--languages-group (ubb--make-group :name "Languages" :sets%% #'ubb--all-languages :header (lambda (set) (format "Language: %s" (ubb--set-name set))))) ;;; Games (important) (defun ubb--all-game-sets () (list (ubb--make-set "Chess" (ubb--string-ranges "=E2=99=9A=E2=99=9B=E2= =99=9C=E2=99=9D=E2=99=9E=E2=99=9F=E2=99=99=E2=99=98=E2=99=97=E2=99=94=E2=99= =96=E2=99=95")) (ubb--make-set "Domino" `((#x1f030 . #x1f0a0))) (ubb--make-set "Poker" `(,@(ubb--string-ranges "=E2=99=A0=E2=99=A4=E2=99= =A5=E2=99=A1=E2=99=A6=E2=99=A2=E2=99=A3=E2=99=A7") (#x1f0a0 . #x1f100))))) (defun ubb--game-header (set) (format "Game: %s" (ubb--set-name set))) (defvar ubb--games-group (ubb--make-group :name "Games" :sets%% #'ubb--all-game-sets :header #'ubb--game-header)) ;;; Groups (defun ubb--all-groups () (list ubb--unicode-blocks-group ubb--charsets-group ubb--scripts-group ubb--unicode-categories-group ubb--languages-group ubb--games-group)) (defvar ubb--read-group-name-history (list "Unicode blocks")) (defun ubb--read-group-name (prompt) (let ((hist 'ubb--read-group-name-history) (completion-ignore-case t)) (completing-read prompt (mapcar #'ubb--group-name (ubb--all-groups)) nil t nil hist))) (defun ubb--find-group-by-name (name) (cl-find name (ubb--all-groups) :key #'ubb--group-name :test #'equal)) (defvar ubb--read-set-name-history (list)) (defun ubb--read-set-name (group prompt) (let ((hist 'ubb--read-set-name-history) (completion-ignore-case t) (completion-styles (cl-adjoin 'substring completion-styles))) (completing-read prompt (mapcar #'ubb--set-name (ubb--group-sets group)) nil t nil hist))) (defun ubb--find-set-by-name (group name) (cl-find name (ubb--group-sets group) :key #'ubb--set-name :test #'equal)) ;;; Display (defconst ubb--space " ") (defconst ubb--thin-space (string #x2009)) ;; Insert space around the string from START to END so that the region ;; occupies approximately 2*FONT-WIDTH pixels. Inserting space is ;; generally a good idea to "neutralize" combining marks. (defun ubb--insert-space (win start end font-width) (let ((pixel-width (car (window-text-pixel-size win start end)))) (cond ((<=3D pixel-width font-width) (save-excursion (goto-char start) (insert ubb--space))) ((< pixel-width (* 2 font-width)) (save-excursion (goto-char start) (insert ubb--thin-space))) (t ;; give up )))) (defface ubb-invisible '((t :inherit tooltip)) "Face used for codepoints that would otherwise be invisible/transparent." :group 'ubb) (defun ubb--propertize (string codepoint) (let ((s (propertize string 'codepoint codepoint 'help-echo #'ubb--help-echo))) (cond ((memq (get-char-code-property codepoint 'general-category) '(Cf Zs Zl Zp)) (propertize s 'face 'ubb-invisible)) (t s)))) (defun ubb--insert-codepoint (win codepoint font-width) (let* ((s (cl-case codepoint (?\n "^J") (t (string codepoint)))) (s (ubb--propertize s codepoint)) (start (point)) (_ (insert s)) (end (point))) (ubb--insert-space win start end font-width))) ;; FIXME: only for compatibility with Emacs 24 (defun ubb--default-font-width () (cond ((fboundp 'default-font-width) (default-font-width)) (t (frame-char-width)))) (defvar ubb--right-margin 10) (defvar ubb--left-margin 1) ;; Insert a set of codepoints, trying to create lines of equal width. ;; ;; The window WIN is needed for pixel measuring functions. Inserting ;; large sets can be slow, so this calls redisplay for every line to ;; give some visual feedback to the user. Also, the progress is shown ;; in percent in the echo area. (defun ubb--insert-set (win set) (let* ((font-width (ubb--default-font-width)) (right-limit (- (window-width win t) (* ubb--right-margin font-width))) (line-start (point)) (set-size (ubb--set-size set))) (insert-char ?\s ubb--left-margin) (ubb--set-foreachi (lambda (i codepoint) (ubb--insert-codepoint win codepoint font-width) (let ((w (car (window-text-pixel-size win line-start (point))))) (when (<=3D right-limit w) (insert "\n") (setq line-start (point)) (insert-char ?\s ubb--left-margin) (message "%.f%%" (* 100.0 (/ (float i) set-size))) (redisplay)))) set) (message nil))) (defun ubb--clear-codepoint-info () (message nil)) ;; Return a short description for codepoint. This basically the ;; Unicode name. (defun ubb--short-description (codepoint) (let* ((name (get-char-code-property codepoint 'name)) (old (get-char-code-property codepoint 'old-name)) (cat (get-char-code-property codepoint 'general-category)) (catdesc (char-code-property-description 'general-category cat))) (format "\"%c\" %s (%s: %s)" codepoint (or name old "[no name]") cat catdesc))) (defun ubb--show-codepoint-info (codepoint) (message "%s" (ubb--short-description codepoint))) (defun ubb--current-codepoint (&optional noerror) (let ((codepoint (get-text-property (point) 'codepoint))) (cond (codepoint) (noerror nil) (t (user-error "No codepoint selected"))))) ;; This is called from post-command-hook. (defun ubb--codepoint-sensor () (unless (current-message) (let ((codepoint (ubb--current-codepoint t))) (cond (codepoint (ubb--show-codepoint-info codepoint)) (t (ubb--clear-codepoint-info)))))) ;; This called if the mouse pointer hovers around. (defun ubb--help-echo (_ __ pos) (let ((codepoint (get-text-property pos 'codepoint))) (if codepoint (ubb--short-description codepoint)))) (defvar ubb--buffer-set) ; The currently displayed ubb--set (defvar ubb--buffer-group) ; The group to which ubb--buffer-set belongs (define-derived-mode ubb-mode fundamental-mode "ubb" "Mode for viewing the characters in Unicode blocks and other charsets." (setq-local bidi-display-reordering nil) (setq-local truncate-lines t) (read-only-mode 1) (add-hook 'post-command-hook 'ubb--codepoint-sensor nil t)) (defun ubb--buffer-name () "*ubb*") (defun ubb--get-buffer () (or (get-buffer (ubb--buffer-name)) (with-current-buffer (get-buffer-create (ubb--buffer-name)) (ubb-mode) (current-buffer)))) (defun ubb--display-set (set) (let* ((inhibit-read-only t)) (erase-buffer) (let* ((block-win (display-buffer (current-buffer))) (_ (select-window block-win)) (pos (point))) (insert "\n") (ubb--insert-set block-win set) (set-window-point block-win pos) (current-buffer)))) ;; This is the main entry point. ;; ;; Insert and display the ubb--set SET belonging to ubb--group GROUP. ;; Create a fresh buffer only if needed. Display the buffer and ;; select its window. Finally return the buffer. (defun ubb--browse-set (group set) (with-current-buffer (ubb--get-buffer) (setq-local ubb--buffer-group group) (setq-local ubb--buffer-set set) (setq header-line-format (funcall (ubb--group-header group) set)) (ubb--display-set set))) ;;; Commands (defun ubb-describe-codepoint-briefly () "Show name and category of the current codepoint." (interactive) (ubb--show-codepoint-info (ubb--current-codepoint))) (defun ubb-describe-codepoint () "Describe the current codepoint." (interactive) (when (ubb--current-codepoint) (describe-char (point)))) (defun ubb--next-set (next) (let* ((set ubb--buffer-set) (group ubb--buffer-group) (all (ubb--group-sets group)) (i (cl-position set all :test #'equal)) (j (funcall next i))) (cond ((and (<=3D 0 j) (< j (length all))) (ubb--browse-set group (elt all j))) (t (user-error "No more sets (in group %S)" (ubb--group-name group)))))) (defun ubb-next-set () "Browse the next set of the group." (interactive) (ubb--next-set #'1+)) (defun ubb-prev-set () "Browse the previous set of the group." (interactive) (ubb--next-set #'1-)) (defun ubb-select-set-by-name (name) "Select the set to browse by name." (interactive (list (ubb--read-set-name ubb--buffer-group "Set name: "))) (let ((set (or (ubb--find-set-by-name ubb--buffer-group name) (user-error "No set with name: %S" name)))) (ubb--browse-set ubb--buffer-group set))) (defun ubb--search-property (prop &optional backward) "Search the next text range where PROP is non-nil. Return the value of PROP. If BACKWARD is non-nil, search backward." (let ((next (cond (backward #'previous-single-char-property-change) (t #'next-single-char-property-change))) (start (point)) (value nil)) (while (progn (goto-char (funcall next (point) prop)) (not (or (setq value (get-char-property (point) prop)) (eobp) (bobp))))) (cond (value) (t (goto-char start) nil)))) (defun ubb-forward-codepoint () "Move cursor to the next codepoint." (interactive) (or (ubb--search-property 'codepoint nil) (user-error "No more codepoints"))) (defun ubb-backward-codepoint () "Move cursor to the previous codepoint." (interactive) (or (ubb--search-property 'codepoint t) (user-error "No more codepoints"))) (defun ubb-browse-block (block &optional codepoint) "Browse the Unicode block BLOCK. Interactively without prefix arg, prompt for the block name. With negative prefix arg, use the character at point to find the corresponding block. With positive positive arg, prompt for the name or number of the codepoint \(see `read-char-by-name')." (interactive (cond ((not current-prefix-arg) (let ((name (ubb--read-set-name ubb--unicode-blocks-group "Block name: "))) (list (or (ubb--find-set-by-name ubb--unicode-blocks-group name) (user-error "No block with named: %S" name))))) (t (let* ((codepoint (cond ((< (prefix-numeric-value current-prefix-arg) 0) (char-after)) (t (read-char-by-name "Codepoint (Unicode name or hex): ")))) (block (or (ubb--find-block-by-codepoint codepoint) (user-error "No block for codepoint: %X" codepoint)))) (list block codepoint))))) (with-current-buffer (ubb--browse-set ubb--unicode-blocks-group block) (when codepoint (search-forward (string codepoint)) (backward-char)))) (defun ubb-browse-block-by-codepoint () "Very similar to `ubb-browse-block'. The only difference is that when invoked without prefix arg, prompt for the codepoint instead for the Unicode block." (interactive) (let ((current-prefix-arg (or current-prefix-arg '(4)))) (call-interactively #'ubb-browse-block))) (defun ubb--browse-group (group) (ubb--browse-set group (elt (ubb--group-sets group) 0))) (defun ubb-browse-group-by-name (name) "Prompt for a group name and display the first set in the group." (interactive (list (ubb--read-group-name "Group name: "))) (let ((group (or (ubb--find-group-by-name name) (user-error "No group with name: %S" name)))) (ubb--browse-group group))) (defun ubb-reset-text-scale () (interactive) (text-scale-set 0)) (defun ubb-redraw () "Redraw the current set." (interactive) (ubb--display-set ubb--buffer-set)) (defun ubb-browse () "Start the character browser." (interactive) (let ((buffer (get-buffer (ubb--buffer-name)))) (cond (buffer (with-current-buffer buffer (ubb--browse-set ubb--buffer-group ubb--buffer-set))) (t (ubb--browse-group ubb--unicode-blocks-group))))) (defun ubb-quit () "Close the UBB window." (interactive) (quit-restore-window nil 'bury)) ;;; Menu ;; Return an uninterned symbol with FUN set as it function. This is a ;; trick to put closures into menus. (defun ubb--fake-menu-symbol (fun) (let ((sym (make-symbol "fake-menu-filter-symbol"))) (fset sym fun) sym)) (defun ubb--build-set-menu (group) (mapcar (lambda (set) (vector (ubb--set-name set) (ubb--fake-menu-symbol (lambda () (interactive) (ubb--browse-set group set))))) (ubb--group-sets group))) (defun ubb--set-menu-filter (_others) (ubb--build-set-menu ubb--buffer-group)) (defun ubb--build-group-menu () (mapcar (lambda (group) (list (ubb--group-name group) :filter (ubb--fake-menu-symbol (lambda (_) (ubb--build-set-menu group))))) (ubb--all-groups))) (easy-menu-define nil ubb-mode-map "Menu for UBB mode." `("Character-Browser" ,@(ubb--build-group-menu) "--" ["Select group by name" ubb-browse-group-by-name] ["Select set by name" ubb-select-set-by-name] ["Select Unicode block by codepoint" ubb-browse-block-by-codepoint] ("Select set in current group" ("Sets in current group" :filter ubb--set-menu-filter) ["Next set in group" ubb-next-set :key-sequence ">"] ["Previous set in group" ubb-prev-set :key-sequence "<"]) "--" ["Describe character briefly" ubb-describe-codepoint-briefly] ["Show character details" ubb-describe-codepoint] ("Movement" ["Move to next character" ubb-forward-codepoint] ["Move to previous character" ubb-backward-codepoint]) ("Zoom" ["Increase scale factor" text-scale-increase] ["Decrease scale factor" text-scale-decrease] ["Reset scale factor" ubb-reset-text-scale]) "--" ["Redraw" ubb-redraw] ["Quit" ubb-quit])) ;;; Key bindings (define-key ubb-mode-map (kbd "") #'ubb-describe-codepoint-briefly) (define-key ubb-mode-map (kbd "D") #'ubb-describe-codepoint) (define-key ubb-mode-map (kbd "f") #'ubb-forward-codepoint) (define-key ubb-mode-map (kbd "b") #'ubb-backward-codepoint) (define-key ubb-mode-map (kbd "n") #'ubb-next-set) (define-key ubb-mode-map (kbd "p") #'ubb-prev-set) (define-key ubb-mode-map (kbd ">") #'ubb-next-set) (define-key ubb-mode-map (kbd "<") #'ubb-prev-set) (define-key ubb-mode-map (kbd "N") #'ubb-select-set-by-name) (define-key ubb-mode-map (kbd "G") #'ubb-browse-group-by-name) (define-key ubb-mode-map (kbd "C") #'ubb-browse-block-by-codepoint) (define-key ubb-mode-map (kbd "+") #'text-scale-increase) (define-key ubb-mode-map (kbd "-") #'text-scale-decrease) (define-key ubb-mode-map (kbd "*") #'ubb-reset-text-scale) (define-key ubb-mode-map (kbd "g") #'ubb-redraw) (define-key ubb-mode-map (kbd "q") #'ubb-quit) (provide 'ubb) --=-=-=--