Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Adding source

  • Loading branch information...
commit f0e14ff52b5f56241cdf335446ddf70d4ab184e1 1 parent db64105
Niels Giesen authored
Showing with 287 additions and 0 deletions.
  1. +287 −0 poor-mans-bidi.el
287 poor-mans-bidi.el
@@ -0,0 +1,287 @@
+;;; poor-mans-bidi.el --- BiDirectional editing mode -*-coding: utf-8;-*-
+;; Copyright (C) 2008 Niels Giesen
+;; <nielsDODOgiesen@gmailDINOSAURcom, with the extinct animals
+;; replaced by dots.>
+;; Author: Niels Giesen nielsDODOgiesen@gmailDINOSAURcom, with
+;; the extinct animals replaced by dots.
+;; Keywords: languages, wp
+;; Version: 0.2
+;; 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 3
+;; of the License, 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
+;; 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., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301, USA.
+;;; Commentary:
+;; This library provides a 'BiDified' mirror of the current
+;; buffer, using an external program such as 'fribidi' (although
+;; for paragraph mode, you might want to switch that to
+;; bidiv). Use
+;; (autoload 'poor-mans-bidi-mode "poor-mans-bidi" "" t)
+;; to have this code autoloaded.
+;; See the documentation for `poor-mans-bidi-mode' for more
+;; information.
+;;; Code:
+(defgroup poor-mans-bidi nil
+ "Customization group for `poor-mans-bidi-mode'."
+ :group 'wp)
+(defcustom poor-mans-bidi-filter-command
+ (lambda () (format "fribidi --nobreak -w %s" fill-column))
+ "Function to return the command invoked by `poor-mans-bidi-decode-buffer'.
+It should handle the conversion from logical->visual ordering,
+ while preserving lines. For this reason, `auto-fill-mode' is
+ turned on as well.
+Pros fribidi:
+- handles characters better than bidiv;
+- --nobreak option is nice to keep line numbers the same across
+Pros bidiv:
+- can interpret on paragraph-level
+The cons are that either doesn't do/have what the other does/has."
+ :group 'poor-mans-bidi
+ :type '(function))
+(defcustom poor-mans-bidi-font-lock-keywords
+ '(("\\([ְֱֲֳִֵֶַָֹֻּֽֿﬞ׀ׁׂ][םמןנבץצחזלךכהגדסףפאיײױװותטרעשק]\\)"
+ (0 (progn (compose-region (match-beginning 1) (match-end 1))
+ nil))))
+ "Font lock keyword rules for BiDi mirror buffers.
+The rules are meant for composing complex characters from base (Unicode) chars
+and punctuation marks (that are used in e.g. Yiddish texts) in the mirror
+buffer showing bidirectional output. If you find other rules useful, please
+send a report, so that they can be included in this file."
+ :group 'poor-mans-bidi
+ :type '(alist))
+(defcustom poor-mans-bidi-minimal-context-length 3
+ "Minimal length of search context (or rather: pretext).
+This variable is used by `poor-mans-bidi-decode-buffer' when locating point in
+ the mirror buffer. Setting this too short may find point at another, like,
+ point, esp. with short palindromes such as the word \"a\" in English or (its
+ translation!) \"א\" in Yiddish in BiDi texts (not in one-way texts) . Set it
+ to 4 if this really bugs you.
+You might also want to change it to something larger to start looking in the
+opposite direction sooner, e.g. when you edit texts that are mainly RTL (or
+LTR), and not so much intertwined. The RTL-direction is searched first:
+assuming that if variable `poor-mans-bidi-mode' is non-nil, you are probably
+editing RTL text."
+ :group 'poor-mans-bidi
+ :type '(integer))
+(defcustom poor-mans-bidi-timer-interval .1
+ "Interval used by `poor-mans-bidi-timer'.
+Set this to a small value (such as 0.01), to have quick response in the mirror
+buffer. Smaller than that is hardly noticeable. Larger values than the default
+might be wise for slower computers.
+When changed, make sure to run \\[poor-mans-bidi-disable-altogether] and
+\\[poor-mans-bidi-mode] again, in order to reset the timer so it will see its
+new interval."
+ :group 'poor-mans-bidi
+ :type '(float))
+(defcustom poor-mans-bidi-inhibited-major-modes
+ '((w3m-mode))
+ "Rules for alternative modes for bidirectional mirror buffers.
+This variable overrides the default behaviour that mirror buffers made with
+the command `poor-mans-bidi-mode' have the same major mode as the major mode
+for the source buffer.
+This variable is an alist of the form ((MAJOR-MODE . ALTERNATIVE-MODE)), where
+ALTERNATIVE-MODE will be the major mode for buffers mirroring buffers where
+MAJOR-MODE is the major mode.
+As a shortcut, if MAJOR MODE is nil, `fundamental-mode' will become the
+major mode for the mirror buffer."
+ :group 'poor-mans-bidi
+ :type '(alist))
+(define-minor-mode poor-mans-bidi-mode
+ "Minor mode for writing bidirectional text.
+Show a 'BiDified' mirror of the current buffer, using external
+ programs such as 'fribidi' (although for paragraph mode, you
+ might want to switch that to bidiv). Adjust the variable
+ `poor-mans-bidi-filter-command' to use another filter.
+In the mirroring buffer, a new point tries to be located merely
+ on context in source buffer, NOT on any knowledge of the
+ algorithm described at url
+Toggle the mirroring with the command \\[poor-mans-bidi-mode].
+ This sets an idle timer doing its work every tenth of a
+ second, so if you are not working on a BiDi text, you'd better
+ issue the command \\[poor-mans-bidi-disable-altogether]
+Could be very well adapted for lisp-only handling however. See
+ comments in the definition for the function
+ `poor-mans-bidi-decode-buffer' if you feel so inclined."
+ nil
+ " BiDi"
+ '(("\C-cb" . poor-mans-bidi-mode))
+ (if poor-mans-bidi-mode
+ (poor-mans-bidi-enable)
+ (poor-mans-bidi-disable)))
+(defvar poor-mans-bidi-timer nil "Timer object.")
+(defvar poor-mans-bidi-tick nil "To compare with output from `buffer-modified-tick'.")
+(defvar poor-mans-bidi-point nil "To compare old point with current (point).")
+(defvar poor-mans-bidi-mirror-buffer nil "Buffer for BiDi display.")
+(defconst poor-mans-bidi-mirror-buffer-postfix "-BiDi")
+(defun poor-mans-bidi-enable ()
+ "Enable BiDi display in mirror buffer."
+ (set (make-local-variable 'poor-mans-bidi-tick)
+ (buffer-modified-tick))
+ (set (make-local-variable 'poor-mans-bidi-point)
+ (point))
+ (set (make-local-variable 'poor-mans-bidi-mirror-buffer)
+ (concat (buffer-name (current-buffer))
+ poor-mans-bidi-mirror-buffer-postfix))
+ (if (timerp poor-mans-bidi-timer)
+ (timer-activate poor-mans-bidi-timer)
+ (setq poor-mans-bidi-timer
+ (run-with-idle-timer poor-mans-bidi-timer-interval poor-mans-bidi-timer-interval
+ (lambda ()
+ (when
+ (and poor-mans-bidi-mode
+ (or (and (boundp 'poor-mans-bidi-tick)
+ (< poor-mans-bidi-tick
+ (buffer-modified-tick)))
+ (not (= poor-mans-bidi-point (point)))))
+ (setq poor-mans-bidi-point (point))
+ (setq poor-mans-bidi-tick (buffer-modified-tick))
+ (poor-mans-bidi-decode-buffer))))))
+ (auto-fill-mode 1)
+ ;; Copy major mode over and add font-locking
+ (let ((mode major-mode))
+ (if (assoc mode poor-mans-bidi-inhibited-major-modes)
+ (setq mode
+ (or (cdr (assoc mode poor-mans-bidi-inhibited-major-modes))
+ 'fundamental-mode)))
+ (save-window-excursion
+ (switch-to-buffer-other-window poor-mans-bidi-mirror-buffer)
+ (funcall mode)
+ (font-lock-mode -1)
+ (poor-mans-bidi-add-font-locking))))
+(defun poor-mans-bidi-disable ()
+ "Disable BiDi display in mirror buffer."
+ (and poor-mans-bidi-mirror-buffer
+ (bufferp (get-buffer poor-mans-bidi-mirror-buffer))
+ (kill-buffer poor-mans-bidi-mirror-buffer)))
+(defun poor-mans-bidi-disable-altogether ()
+ "Disable BiDi altogether.
+This command disables BiDi altogether, until function `poor-mans-bidi-mode' is
+ called again with a positive argument. Its foremost use is to kill the
+ timer. Kills all associated BiDi-mirror buffers too."
+ (interactive)
+ (when (timerp poor-mans-bidi-timer)
+ (cancel-timer poor-mans-bidi-timer))
+ (setq poor-mans-bidi-timer nil)
+ (mapcar (lambda (b)
+ (when (and
+ (buffer-live-p b)
+ (string-match
+ (format "%s$"
+ poor-mans-bidi-mirror-buffer-postfix)
+ (buffer-name b)))
+ (kill-buffer b))
+ (when poor-mans-bidi-mode (poor-mans-bidi-mode -1))) (buffer-list)))
+(defun poor-mans-bidi-decode-buffer ()
+ "Send buffer to filter defined by `poor-mans-bidi-filter-command'."
+ (let ((line-number (line-number-at-pos))
+ (column (current-column))
+ (context (buffer-substring-no-properties
+ (point-at-bol)
+ (point)))
+ (buffer (current-buffer))
+ (mode major-mode))
+ ;; change following sexp to adopt to Lisp-only logical->visual
+ ;; conversion of our buffer. Just make sure to put the output in
+ ;; `poor-mans-bidi-mirror-buffer', then it will work.
+ (shell-command-on-region (point-min)
+ (point-max)
+ (funcall poor-mans-bidi-filter-command)
+ poor-mans-bidi-mirror-buffer)
+ ;; Here's a stab at what I just said. It works roughly;
+ ;; however `bidi-logical-to-visual' is not perfect (yet, as
+ ;; this mode isn't either), so I'll rely on fribidi for now.
+ ;;
+ ;; (save-window-excursion
+ ;; (let ((str (bidi-logical-to-visual (buffer-string))))
+ ;; (switch-to-buffer-other-window poor-mans-bidi-mirror-buffer)
+ ;; (erase-buffer)
+ ;; (insert str)))
+ (switch-to-buffer-other-window poor-mans-bidi-mirror-buffer)
+ (goto-line line-number)
+ ;; Reductively try and find position by context as defined by beginning of
+ ;; source line up until point -- and smaller each run. Stop search when
+ ;; length of the search string equals the value of
+ ;; `poor-mans-bidi-minimal-context-length'. Begin search RTL.
+ (end-of-line)
+ (unless (< poor-mans-bidi-minimal-context-length
+ (length (do ((str context (substring str 1)))
+ ((or (search-backward (poor-mans-bidi-string-reverse
+ str)
+ (point-at-bol) t)
+ (< (length str) poor-mans-bidi-minimal-context-length))
+ str))))
+ (progn (beginning-of-line)
+ (do ((str context (substring str 1)))
+ ((or (search-forward str (point-at-eol) t)
+ (< (length str) poor-mans-bidi-minimal-context-length))
+ str))))
+ (switch-to-buffer-other-window buffer)))
+(defun poor-mans-bidi-add-font-locking ()
+ "Add font locking to BiDi mirror buffer.
+See also variable `poor-mans-bidi-font-lock-keywords'."
+ (unless (and (boundp 'auto-composition-mode)
+ auto-composition-mode)
+ (font-lock-add-keywords
+ nil poor-mans-bidi-font-lock-keywords)
+ (font-lock-mode 1)))
+(defun poor-mans-bidi-remove-font-locking ()
+ "Remove font locking to BiDi mirror buffer.
+See also variable `poor-mans-bidi-font-lock-keywords'."
+ (font-lock-remove-keywords
+ nil poor-mans-bidi-font-lock-keywords)
+ (font-lock-mode 1))
+(defun poor-mans-bidi-string-reverse (str)
+ (mapconcat 'identity (reverse (split-string str "")) ""))
+(provide 'poor-mans-bidi)
+;;; poor-mans-bidi.el ends here
Please sign in to comment.
Something went wrong with that request. Please try again.