;;; owl-motion.el --- a part of the simple OWL mode for Xemacs ;;; (C) 2001 BBN Technologies ;;; by Mark Burstein, with assistance from Ken Anderson and Richard Shapiro ;;; This package is designed to be used with XEmacs and Gnu Emacs ;;; motion commands analogous to lisp structure movements ;;; support C-M-f, C-M-b - forward and back over tags (:: sexpressions) ;;; C-M-e (END), C-M-a (BEGINING) - end and beginning of enclosing tag expression ;;; (using the same heuristic that tag on left margin (not indented) starts a definition) ;;; C-M-i - (IN) down an embedding level ;;; C-M-u, C-M-d - up and down definitions ;;; and down goes just after it (or does nothing if no more levels). ;;; - OWL-INDENT-LINE ;;; C-M-q - INDENT DEFINITION ;;; forward and backward may cross between embedding levels if this var is T ;;; otherwise, stop when moving forward over an end tag or backward over a ;;; non-empty (that is, non self-terminating) begin tag. (defvar *owl-move-cross-levels* nil "If T then fwd, back motions will cross embedding levels.") (defvar *owl-definition-matcher* nil "If non-nil, then the definition match string") (make-variable-buffer-local '*owl-definition-matcher*) (defvar *owl-definition-indent* nil "If non-nil, then the definition indent column.") (make-variable-buffer-local '*owl-definition-indent*) (defvar *def-begin-pattern* "<[^!/?]") (defun owl-definition-matcher () (or *owl-definition-matcher* (let ((str *def-begin-pattern*)) (dotimes (i (owl-definition-indent)) (setq str (concat " ?" str))) (setq *owl-definition-matcher* (concat "^" str))))) (defun owl-definition-indent () (or *owl-definition-indent* (let ((curpos (point))) (beginning-of-buffer) (when (and (re-search-forward "" nil t))) (owl-search-fwd "<") (backward-char 1) (setq *owl-definition-indent* (current-column))))) ; (owl-def-begin-string *owl-definition-indent*) (defun owl-beginning-of-def-p () (let ((curpos (point)) ok) (backward-char (owl-definition-indent)) (setq ok (looking-at (owl-definition-matcher))) (goto-char curpos) ok)) ;;; ignore balanced dbl-quote strings and xml comments if ignore-comment-p ;;; for now don't worry about quoted dblquote inside strings ;;; number of dbl quote chars between begin of line and (point) (defun dblqts-before (&optional pt) (let* ((orig-pos (point)) (end (or pt orig-pos)) (cnt 0)) (beginning-of-line) (while (< (point) end) (if (char= (char-after) ?\") (incf cnt)) (forward-char)) (if (and pt (/= pt orig-pos)) (goto-char orig-pos)) cnt)) ;;; Determine if inside a quoted string. ;;; Assumes strings on single line only for simplicity. ;;; Therefore if odd number of quotes before ;;; on the line, then inside one. (defun inside-string-p () (oddp (dblqts-before))) (defvar owl-search-no-error t) ;;; search forward or return OWL-specific error provided (defun search-forward-or-err (string &optional errstr) (let ((pt (point)) (res (search-forward string nil owl-search-no-error))) (if res res (error (or errstr (format "Syntax Error: Missing %s after %s" string pt)))))) ;;; search backward or return OWL-specific error provided (defun search-backward-or-err (string &optional errstr) (let ((pt (point)) (res (search-backward string nil owl-search-no-error))) (if res res (error (or errstr (format "Syntax Error: Missing %s before %s" string pt)))))) ;;; look forward (backward) to find the closest angle bracket ;;; find the nearest < or > in direction (bkward or not) ;;; if move is non-nil, then move if found char = move or move=T ;;; if move is a char, then signal an error if not the right one. ;;; return dotted pair of < or > and position of the char. (defun closest-bracket-pos (&optional bkward move) (let* ((curpos (point)) (pos (if bkward (search-backward-regexp "[><]" nil t) (when (search-forward-regexp "[><]" nil t) (backward-char) ;; if going forward, move before the char (point)) ;; and return the pos )) (chr (and pos (char-after pos)))) (cond ((null chr) nil) (t (unless (or (eq move t)(eq move chr)) (goto-char curpos)) (cons chr pos))))) ;;; search forward for a regexp, skipping quoted strings and balanced xml and owl/rdf comments ;;; skip quoted strings and xml comments. Must start outside an xml comment. ;;; like regular search, put point at the end of the expression when done. (defun owl-search-fwd (regexp &optional pt-to-beginning) "Regexp search forward skipping quoted strings and XML, RDF comments." (interactive "s") (let ((found nil) (in-quote (inside-string-p))) (while (and (not found) (not (eobp))) (let ((char (char-after))) (cond ((eobp) nil) (in-quote (when (search-forward-regexp "[^\\]\"" nil t) (backward-char) (setf in-quote nil))) ((and (char= char ?\") (not (char= (char-before) ?\"))) (search-forward-regexp "[^\\]\"" nil t) (backward-char)) ((looking-at "" nil t)) ((looking-at "") (search-forward-regexp "" nil t)) ((looking-at regexp) (unless pt-to-beginning (search-forward-regexp regexp));; to move point (setq found (point))))) (unless found (forward-char))) found)) ;;; search backward for a regexp, skipping quoted strings and balanced xml and owl comments ;;; skip quoted strings and xml comments. Must start outside an xml comment. ;;; start-prev is how many chars before (point) to start looking-at. (defun owl-search-bkwd (regexp &optional start-prev) "Regexp search backward skipping quoted strings and XML, RDF comments." (interactive "s") (let ((curpos (point)) (found (if (bobp) (error "Beginning of buffer.") nil))) ;; backup enough to match forward the first time (should be passed in unless exact match) (or start-prev (setf start-prev 1)) (backward-char start-prev) (unwind-protect (let ((in-quote (inside-string-p))) (while (and (not found)(not (bobp))) (let ((char (char-after))) (cond ;; if in-quote ignore char (in-quote (search-backward-or-err "\"")) ((looking-at "-->") (search-backward-or-err "\\)" nil t)) (setf in-wd (and curpos2 cpos (< (- curpos2 cpos) (- (match-end 1)(match-beginning 1))))) (cond (cpos (setf com (buffer-substring (match-beginning 1)(match-end 1))) ; (dbg "cpos=%s" com) (cond ((string-match ""))) cpos) ((string-equal com ""))) cpos) ((and (string-equal com "-->") in-wd) (setq cpos (if (eq move 'end) (search-forward-or-err "-->") (search-backward-or-err "" com)) (search-backward-or-err " or ) ;;; ;(search-forward-regexp "<\\(\w[\w:-_]*\\)")) ;;; OWL-FORWARD-TAG (c-m-f) ;;; move to end of current tag or over a complete (balanced) tag pair ;;; prefix arg N is number of tag pairs to skip ;;; if in a tag, go to end of end tag ;;; if before a begin tag go to end of matching end ;;; if before an end, then dont move unless *owl-move-cross-levels* ;;; as this is a level change. ;;; should return position if moved? (defun owl-forward-tag (&optional n) "Move forward to the end of the current tag, or over a balanced tag if between tags." (interactive "_p") (if (null n) (setq n 1)) (let ((curpos (point))) (condition-case err (let ((cp2 (point))) (owl-beginning-of-comment-if-inside) ;; else dont move (while (> n 0) (decf n) ;; if inside a tag (begin or end), go to front of it, else next < (when (owl-goto-tag-begin) (cond ((looking-at "")) ((looking-at "") (search-forward-or-err "")) ;; at an end tag, just go after it ;; - UNLESS PROHIBITED BY *OWL-MOVE-CROSS-LEVELS* ;; which is now normally true. ((looking-at "") (goto-char cp2);; just stay put where we were. nil)) ;; else should be a begin tag, need to find end ((looking-at "<[?!]") (owl-skip-xml-tag-group)) (t ; (dbg "in fwd tag bf skip pt=%s\n" (point)) (owl-skip-tag-group))))) (if (eq (point) curpos) nil (point)) ) (error ;; if an error, ; (dbg "error %s" err) (cond ((not owl-search-no-error) (error (format "%s" err))) (t (goto-char curpos) (message (format "%s" err)) ;;; (error (second err)) nil))) ))) ;;; Tag begins after point. Find corresponding end tag and go after it ;;; Assumes we are not in a comment here. (defun owl-skip-tag-group (&optional tag) (unless tag (setf tag (owl-read-token))) (let* ((curpos (point)) (tagbe (search-forward-or-err ">" )) (tag-regexp (concat "]")) ;; match <{/}tag{separator or >} tage ) ;;; (trace-msg "at char %s\n" (point)) (cond ((null tagbe) (error "Missing > for tag %s" tag)) ((char= (char-before (1- (point))) ?/) ;; empty end tag (point)) ;; just stay after /> (t (owl-find-matching-tag-end tag-regexp))))) ;;; skip to end of matching > -- this is conveniently done by forward-list because <> are seen like () (defun owl-skip-xml-tag-group () (forward-list)) ;;; starting from front of a begin tag, find corresponding full end tag ;;; regexp is the tag to search for (defun owl-find-matching-tag-end (regexp) (let ((cnt 1) (matchpt t)) (while (and (> cnt 0) matchpt) (setf matchpt (owl-search-fwd regexp t)) ;; set point at beginning of match ;; no match is an error (unbalanced tags) (cond ((null matchpt) (error "Couldn't find tag %s" regexp)) ((looking-at "")) ) ;; a begin tag, go after it, and down a level unless empty end ((setf matchpt (search-forward-or-err ">")) (when matchpt (unless (and (numberp matchpt) (char= (char-before (1- matchpt)) ?/)) (incf cnt)))))) matchpt)) ;;; match to chars before point. e.g if abc^def where (point)=^ then ;;; (looking-back-at "abc") is true ;;; (exact match for now). (defun looking-back-at (chars) (let ((curpos (point)) (len (length chars))) (when (and (plusp len)(<= len curpos)) (backward-char len) (prog1 (looking-at chars) (goto-char curpos))))) ;;; OWL-BACKWARD-TAG (c-m-b) ;;; if within begin tag, go to beginning of the current begin tag ;;; if within end tag or after one, skip back to before corresponding begin tag ;;; Don't move back over a begin from directly after if ;;; and *owl-move-cross-levels* is NIL (handled in OWL-GOTO-TAG-BEGIN) ;;; if within or after comment, skip to before comment ;;; function returns new position or nil if no movement ;;; (though I'm not totally sure I'm handling all errors correctly yet. ) (defun owl-backward-tag (&optional n) "If within a tag, move before it. If between tags at an embedding level, move prior to begin tag of surrounding tag layer." (interactive "_p") (if (null n) (setq n 1)) (let ((curpos (point)) okpos) (condition-case err (progn (while (> n 0) (setq okpos (point)) ;; start of each iteration (decf n) (let ((empty-endp nil) (newpos nil) back) ;; if inside a tag (begin or end or comment), go to the < ;; if after tag (owl-goto-tag-begin 'back) goes directly AFTER the preceding > (if any) ;; owl-goto-tag-begin returns (char . pos) if moved where char is ?> or ?< (when (setf back (owl-goto-tag-begin 'back)) ; (dbg "after tag-begin back= %s looking-at %s pt= %s" back (bs 5) (point)) (when (and back (eq (car back) ?>)) ;; between tags, so backed up to just after a > ;; jump to beginning of tag, unless jumping over a non-empty begin tag ;; (that is, a begin not terminated by />) ;; which would mean changing levels (only do it if *OWL-MOVE-CROSS-LEVELS*) (cond ((looking-back-at "-->") (setq newpos (search-backward "") (owl-search-bkwd "