[comp.emacs] Pli bona dulingva teksttajpado / Better bilingual editing

pfeiffer@deva.irit.fr (Daniel Pfeiffer) (04/25/91)

Anta^u iaj tagoj mi sendis		Some days back, I sent a program for
programeron por dulingva tajpado	bilingual editing in Emacs.  I have
kiel en ^ci artikolo sub Emacs.  Mi	since received some ideas and
ricevis iajn ideojn kaj demandojn de	questions from you, netters.  Along
vi, retanoj.  Kune kun miaj spertoj	with my experiences, this has
uzante ^gin, tio permesis al mi ^gin	allowed me to improve it quite a
plibonigi multe.  Pluraj bufroj nun	bit.  Multiple buffers are no longer
ne plu estas probleme, kaj ekzistas	a problem, and there are now various
pliaj helpoj por uzi ^gin.  Du uloj	aids available.  Two guys asked me
demandis al mi kial mi ^gin celis al	why this was bilingual oriented
dulingva tajpado, ^cu estus problemo	since it seemed to work for any two
^generale tajpi dukolumne?  Fakte	column job.  In fact, it does, so I
ne, ^gi ta^ugas por ^cia dukolumna	renamed it appropriately:
laboro, do mi renomis ^gin tiel:

two-column.el

Amuzu vin kun ^gi kaj signalu iaj	Play with it, and let me know about
ideoj a^u plendoj al mi!		any ideas or complaints!

--
-- Daniel Pfeiffer				<pfeiffer@cix.cict.fr>
-- Tolosa (Toulouse), Midi-Pyrenees, Europe	<pfeiffer@irit.fr>
-- "Beware - polyglot esperantist"		<pfeiffer@frcict81.bitnet>
--

      N
    _---_
   /	 \	NEWS, it goes around the world.
W (-------) E	(sorry, my bitmap doesn't have a world-class resolution)
   \_	_/
     ---
      S

--8<--------8<--------8<--------8<--------8<--------8<--------8<--------8<---
; Esperanto:				English:

; Minora modalo por samtempa dukolumna	Minor mode for simultaneous
; tajpado				two-column editing

; Daniel Pfeiffer <pfeiffer@cix.cict.fr, @irit.fr>, 1991-04-25
; Copyright (C) 1991 Free Software Foundation, Inc.

; ^Ci dataro estas ero de GNU Emacs.	This file is part of GNU Emacs.

; GNU  Emacs  estas  disdonata   en la	GNU Emacs is distributed in the hope
; espero  ke ^gi estos utila,  sed SEN	that it will  be useful, but WITHOUT
; IA  GARANTIO.   Neniu   a^utoro  a^u	ANY    WARRANTY.     No  author   or
; disdonanto  akceptas respondecon  al	distributor   accepts responsibility
; iu ajn  por la sekvoj de ^gia uzado,	to  anyone for the   consequences of
; a^u  ^cu  ^gi serveblas al  iu celo,	using it or  for  whether it  serves
; a^u e^c  entute funkcias,  se  li ni	any particular purpose   or works at
; estas skribinta  tion.  Vidu la  GNU	all,  unless he says so  in writing.
; Emacs ^Generala Publika  Licenco por	Refer    to the    GNU Emacs General
; plenaj detaloj.			Public License for full details.

; ^Ciu rajtas  kopii,  modifi kaj  ree	Everyone is  granted   permission to
; disdoni  GNU Emacs,  sed nur  sub la	copy, modify  and  redistribute  GNU
; condi^coj  priskribitaj  en  la  GNU	Emacs, but only under the conditions
; Emacs  ^Generala  Publika   Licenco.	described in  the  GNU Emacs General
; Kopio de  tiu licenso estas supozata	Public  License.   A copy   of  this
; donita al vi kune kun GNU Emacs, por	license  is supposed to have    been
; ke    vi  sciu  viajn  rajtojn   kaj	given to you along with GNU Emacs so
; respondecojn.   ^Gi  devus  esti  en	you   can   know   your   rights and
; dataro     nomata    COPYING.  Inter	responsibilities.  It should be in a
; alia^joj,  la notico  pri  kopirajto	file named    COPYING.   Among other
; kaj  ^ci  notico devas esti  gardata	things, the  copyright   notice  and
; sur ^ciuj kopioj.			this notice must be preserved on all
;					copies.


; Tiu minora  modalo  ebligas  al   vi	This     minor mode  allows  you  to
; tajpi   sendepende  en   du   apudaj	independently    edit two   adjacent
; bufroj.  Vi  havas tri eblecojn  por	buffers.    You have three  ways  to
; eki    ^gin.   ^Ciu  donas  al    vi	start it  up.   Each  gives   you  a
; horizontale   disigatan   fenestron,	horizontally split window similar to
; simila  al  fina   apareco  de   via	the final outcome of your text:
; teksto:
;					C-x 6 2 associates a new buffer called
; C-x 6 2 asocias novan bufron nomatan		the same, but with 2C/ prepen-
; 	same, sed kun 2C/ anta^u.		ded.
; C-x 6 b asocias alian bufron.  Vi	C-x 6 b associates another buffer.
; 	povas anka^u asocii dataron,		This can be used to associate
; 	se vi ^jus anta^ue faris		a file if you just did
; 	C-x C-f.				C-x C-f.
; C-x 6 u disigas jam dukolumnan	C-x 6 u unmerges a two-column text
; 	tekston en du bufroj ekde la		into two buffers from the
;	nuna linio, kaj je la nuna		current line and at the
;	kolumno.  La anta^ua signo		current column.  The preceding
;	(ofte tabeligilon a^u |) estas		character (often tab or |) is
;	la kolumna disiganto.  Linioj		the column separator.  Lines
;	kiuj ne enhavas ^gin ne estas		that don't have it won't be
;	disigitaj.  Kiel la kvara kaj		separated.  Like the fourth
;	la kvina linio se vi disigas		and fifth line if you unmerge
;	^ci dataron ekde la unua angla		this file from the first
;	vorto.					english word.

; Prefiksa argumento al  iu de la  tri	A  prefix argument to   any of these
; komandoj  signifas  ke Emacs metu la	three commands means that Emacs  put
; nunan  bufron  en la duan  kolumnon,	the  current  buffer into the second
; an^stata^u en la unuan.		column, instead of into the first.

; Je ^cia  flanko  estas   bufro,  kiu	On each side  is a buffer that knows
; konas  la alian.  Kun la ordonoj C-x	about the  other.  With the commands
; 6 SPC,  C-x 6 DEL kaj  C-x 6 RET oni	C-x  6 SPC, C-x  6 DEL and C-x 6 RET
; povas    suben- a^u  supreniri   unu	you can simultaneously  scroll up or
; ekranon,     kaj   subeniri  linion,	down by a screenfull  and by  a line
; samtempe en la du bufroj.  Per C-x 6	in both buffers.  With C-x 6 C-l you
; C-l vi povas recentrigi  la  linion.	can recenter    the line.  When  you
; Kiam vi nur  plu  havas unu el la du	only have  one of  the   two buffers
; bufroj surekrane vi revidos la alian	onscreen  you can get the other back
; per denove C-x 6 2.			with C-x 6 2 once more.

; Kiu   bufro   estas   dekstre    a^u	Which buffer is right or left on the
; maldekstre   ne gravas  por  la fina	screen  has no   importance  for the
; rezulto.  Sed  se   vi  volas   meti	final  outcome.    However,  if  you
; longajn liniojn  (ekz. programerojn)	include  long lines,  i.e which will
; en  la kunigotan tekston,  ili devas	span both columns (eg. source code),
; esti en la estonte unua kolumno.  La	they should  be in what  will be the
; alia  devas  havi malplenajn  linion	first column,  with  the  associated
; apud ili.				buffer  having  empty lines  next to
;					them.

; Averto: en Emacs kiam vi ^san^gas la	Attention:  in Emacs when you change
; ma^joran modalon, la minoraj modaloj	the major mode,  the minor modes are
; estas  anka^u  elmemorigitaj.   Tiu-	also  purged  from  memory.  In that
; okaze  vi devas religi la du bufrojn	case you   must  reassociate the two
; per C-x 6 b.				buffers with C-x 6 b.

; Kiam   vi   estos  kontenta   de  la	When you have edited both buffers to
; rezulto, vi kunmetos la du  kolumnoj	your  content,  you merge them  with
; per C-x 6 1.  Tiel vi metas la unuan	C-x 6 1.  This  puts the  first line
; linion  de la alia  bufro dekstre de	of the other buffer  to the right of
; la nuna  linio.  Se  vi poste  vidas	the current line.  If you then see a
; problemon, vi  neniigu   la kunmeton	problem, you undo the merge with C-x
; per C-x u  kaj  plue  modifu  la  du	u  and  continue   to edit  the  two
; bufrojn.  Kiam vi ne plu volas tajpi	buffers.  When you no longer want to
; dukolumne,  vi  eliru el  la  minora	edit  in  two  columns, you turn off
; modalo per C-x 6 k.			the minor mode with C-x 6 k.


(provide 'two-column)

(defvar two-column:prefix "\C-x6"
  "Prefix two-column:mode-map gets bound to.")

(defvar two-column:mode-map nil
  "Keymap useful with two-column minor mode.")

(if two-column:mode-map
    ()
  (setq two-column:mode-map (make-sparse-keymap))
  (define-key two-column:mode-map "1" 'two-column:merge)
  (define-key two-column:mode-map "2" 'two-column:split)
  (define-key two-column:mode-map "b" 'two-column:associate-buffer)
  (define-key two-column:mode-map "k" 'two-column:kill-association)
  (define-key two-column:mode-map "\C-l" 'two-column:recenter)
  (define-key two-column:mode-map "o" 'two-column:other)
  (define-key two-column:mode-map "u" 'two-column:unmerge)
  (define-key two-column:mode-map " " 'two-column:scroll-up)
  (define-key two-column:mode-map "\^?" 'two-column:scroll-down)
  (define-key two-column:mode-map "\C-m" 'two-column:scroll-line))

(global-set-key two-column:prefix two-column:mode-map)


; markers seem to be the only buffer-id not affected by renaming
; a buffer
(defvar two-column:other nil
  "Marker to the other buffer which will be merged to this one, if non-nil.")
(make-variable-buffer-local 'two-column:other)


(setq minor-mode-alist (cons '(two-column:other " 2C") minor-mode-alist))


; rearranged, so that the pertinent info will show in 40 columns
(defvar two-column:mode-line-format
	'("-%*- %15b --"  (-3 . "%p")  "--%[("  mode-name
	  minor-mode-alist  "%n"  mode-line-process  ")%]%-")
  "*Value of mode-line-format for a buffer in two-column minor mode.")


(defvar two-column:window-width 40
  "*The width of the first column.")


(defvar two-column:fill-column 36
  "*Value of fill-column for a buffer in two-column minor mode.")


(defvar two-column:mode-hook nil
  "Function called, if non-nil, whenever two-column:split is called,
on both buffers if they are not already in two-column minor mode.")

;;;;; base functions ;;;;;


(defun two-column:split (arg &optional buffer)
  "Split current window vertically for two-column editing.  When called
the first time, associates the buffer *Two-Column* with the current
buffer.  Both buffers are put in two-column minor mode and
two-column:mode-hook gets called on both.  These buffers remember about
one another, even when renamed.  When called again, restores the
screen layout with the current buffer first and the associated buffer
to it's right.  Prefix means to put current buffer to the right.

Which buffer is right or left on the screen has no importance for the
final outcome.  However, if you include long lines, i.e which will
span both columns (eg. source code), they should be in what will be
the first column, with the associated buffer having empty lines next
to them.

You have the following commands at your disposal:

\\[two-column:split]   Rearrange screen
\\[two-column:associate-buffer]   Reassociate buffer after changing major mode
\\[two-column:scroll-up]  Scroll both buffers up by a screenfull
\\[two-column:scroll-down]  Scroll both buffers down by a screenful
\\[two-column:scroll-line]  Scroll both buffers up by one or more lines
\\[two-column:recenter]  Recenter and scroll other buffer by same amount
\\[two-column:other]  Switch to associated buffer
\\[two-column:merge]  Merge the other buffer, starting next to this line

These keybindings can be customized in your ~/.emacs by two-column:prefix
and two-column:mode-map.

The appearance of the screen can be customized by the variables
two-column:window-width, two-column:fill-column,
two-column:mode-line-format and truncate-partial-width-windows."

  (interactive "P")
  ; first go to full width, so that we can certainly split into
  ; two windows
  (if (< (window-width) (screen-width))
      (enlarge-window-horizontally 99999))
  (split-window nil two-column:window-width t)

  (if two-column:other
      (progn
	(or arg
	    (other-window 1))
	(switch-to-buffer (marker-buffer two-column:other))
	(other-window (if arg 1 -1)))

    ; set up minor mode linking the two buffers
    (setq fill-column two-column:fill-column
	  mode-line-format two-column:mode-line-format)
    (run-hooks two-column:mode-hook)
    (let ((other (point-marker)))
	(or arg
	    (other-window 1))
	(switch-to-buffer
	 (or buffer
	     (generate-new-buffer (concat "2C/"
					  (buffer-name (current-buffer))))))
	(or buffer
	    (text-mode))
	(setq fill-column two-column:fill-column
	      mode-line-format two-column:mode-line-format
	      two-column:other other
	      other (point-marker))
	(run-hooks two-column:mode-hook)
	(other-window (if arg 1 -1))
	(setq two-column:other other))))

(fset 'two-column:mode 'two-column:split)


(defun two-column:associate-buffer (arg)
  "Prompts for a buffer to associate with this one, and puts both in
two-column minor mode.  Can also be used to associate a just previously
visited file, by accepting the proposed default buffer."
  (interactive "P")
  (and two-column:other
       (marker-buffer two-column:other)
       (error "Buffer already in two-column minor mode."))
  (let ((buffer (read-buffer "Associate buffer: " (other-buffer) t)))
    (save-excursion
      (set-buffer buffer)
      (and two-column:other
	   (bufferp (marker-buffer two-column:other))
	   (error "Buffer already in two-column minor mode.")))
    (two-column:split arg buffer)))


(defun two-column:unmerge (arg)
  "Unmerge a two-column text into two buffers, both in two-column minor mode."
  (interactive "P")
  (if two-column:other
      (error "Buffer already in two-column minor mode."))
  (if (= two-column:window-width (current-column))
      ()
    (make-variable-buffer-local 'two-column:window-width)
    (setq two-column:window-width (current-column)))
  (two-column:split arg)
  (save-excursion
    (let ((other (marker-buffer two-column:other))
	  (char (preceding-char))
	  (n 0)
	  (goal-column (current-column))
	  point)
      (while (not (eobp))
	(if (not (and (eq char (preceding-char))
		      (eq (current-column) goal-column)))
	    (setq n (1+ n))
	  (setq point (point))
	  (backward-char)
	  (skip-chars-backward " \t")
	  (delete-region point (point))
	  (setq point (point))
	  (insert-char ?\n n)
	  (append-to-buffer other point (progn (end-of-line) (1+ (point))))
	  (delete-region point (point))
	  (setq n 0))
	(next-line 1)))))


(defun two-column:kill-association ()
  "Turn off two-column minor mode in current and associated buffer.
If the associated buffer is unmodified and empty, it is killed."
  (interactive)
  (or two-column:other
      (error "Buffer not in two-column minor mode."))
  (let ((b1 (current-buffer))
	(b2 (marker-buffer two-column:other)))
    (kill-local-variable 'two-column:other)
    (kill-local-variable 'two-column:window-width)
    (kill-local-variable 'mode-line-format)
    (kill-local-variable 'fill-column)
    (set-buffer b2)
    (and two-column:other
	 (or (not (marker-buffer two-column:other))
	     (eq b1 (marker-buffer two-column:other)))
	 (if (and (not (buffer-modified-p))
		  (eobp) (= (point) 1))
	     (kill-buffer b2)
	   (kill-local-variable 'two-column:other)
	   (kill-local-variable 'two-column:window-width)
	   (kill-local-variable 'mode-line-format)
	   (kill-local-variable 'fill-column)))))


; this doesn't use yank-rectangle, so that the first column can
; contain long lines
(defun two-column:merge ()
  "Merges the associated buffer to the right of the current buffer, at the
column, which is the value of two-column:window-width.
You should have split the window with \\[two-column:split], in which case
the effect is as though you erase the vertical window separator.  That is,
the two columns are pasted side by side, with the first line of column 2
appearing next to the current line of column 1, in a single text."
  (interactive)
  (or two-column:other
      (error "You must first set two-column minor mode."))
  (save-excursion
    (let ((b1 (current-buffer))
	  (b2 (marker-buffer two-column:other))
	  string)
      (set-buffer b2)
      (goto-char (point-min))
      (while (not (eobp))
	(setq string (buffer-substring (point)
				       (progn (end-of-line) (point))))
	(or (eobp)
	    (forward-char))	; next line
	(set-buffer b1)
	(if (string= string "")
	    ()
	  (end-of-line)
	  (indent-to-column two-column:window-width)
	  (insert string))
	(next-line 1)		; add one if necessary
	(set-buffer b2))
      (set-buffer b2)))
  (if (< (window-width) (screen-width))
      (enlarge-window-horizontally 99999)))

;;;;; utility functions ;;;;;


(defun two-column:other ()
  "Switch to associated buffer."
  (interactive)
  (or two-column:other
      (error "You must set two-column minor mode."))
  (if (get-buffer-window (marker-buffer two-column:other))
      (select-window (get-buffer-window (marker-buffer two-column:other)))
    (switch-to-buffer (marker-buffer two-column:other))))


(defun two-column:scroll-line (arg)
  "Scroll current window and associated window upward by ARG lines."
  (interactive "p")
  (or two-column:other
      (error "You must set two-column minor mode."))
  (scroll-up arg)
  ; too bad that pre 18.57 Emacs makes save-window-excursion restore
  ; the point.  When it becomes extinct, we can simplify this.
  (if (get-buffer-window (marker-buffer two-column:other))
      (let ((window (selected-window)))
	(select-window (get-buffer-window (marker-buffer two-column:other)))
	(unwind-protect
	    (scroll-up arg)
	  (select-window window)))))


(defun two-column:scroll-up (arg)
  "Scroll current window and associated window upward by ARG screens."
  (interactive "p")
  (two-column:scroll-line (* arg (- (window-height)
				    next-screen-context-lines 1))))


(defun two-column:scroll-down (arg)
  "Scroll current window and associated window downward by ARG screens."
  (interactive "p")
  (two-column:scroll-line (* arg (- next-screen-context-lines
				    (window-height) -1))))


(defun two-column:recenter (arg)
  "Center point in window.  With ARG, put point on line ARG.
The associated window is scrolled by the same amount."
  (interactive "P")
  ; is recenter as complicated as this?  I must be missing a base fn
  (setq arg (and arg (prefix-numeric-value arg)))
  (let* (point
	 (distance (count-lines (point)
				(save-excursion
				  (move-to-window-line
				   (cond ((null arg)  nil)
					 ((< arg 0)  -1)
					 (  0)))
				  (setq point (point))))))
    (two-column:scroll (cond ((null arg)
			      (if (> point (point))
				  (- distance)
				distance))
			     ((< arg 0)  (- -1 arg distance))
			     (  (- distance arg))))))