1. camlspotter
  2. offside-trap

Source

offside-trap / offside-trap.el

; In your .emacs:
;
; ;; (load "offside-trap.el") is NOT good. It must be loaded 
; ;; after loading "haskell-indentation.el" 
; (autoload 'offside-trap-mode (expand-file-name "~/offside-trap.el"))
;
; (add-hook 
;  'haskell-mode-hook
;  '(lambda ()
;     (turn-on-haskell-doc-mode)
;     (turn-on-haskell-indentation)
;
;     ;; this must be called after turn-on-haskell-indentation or haskell-indentation-mode,
;     ;; so that offside-trap-mode-map overrides haskell-indentation-mode-map
;     (offside-trap-mode t) 
;     ))

(eval-when-compile (require 'cl)) ; for `destructuring-bind'

;; the overlay
(defvar offside-trap-overlay (make-overlay 1 1))
(defface offside-trap-face
  '((t (:background "#000055")))
  "Face for offside-trap highlight"
  :group 'offside-trap)
(overlay-put offside-trap-overlay 'face 'offside-trap-face)

;; get the line string at the point
(defun offside-trap-string-of-line ()
  (buffer-substring-no-properties
   (line-beginning-position) (line-end-position)))

;; check the line is "sticky"
(defun offside-trap-is-sticky-line (chars)
  (or (string-match "^[ ]*$" (offside-trap-string-of-line))
      (<= chars (current-indentation))))

;; goto line
(defun offside-trap-goto-line (line)
  (goto-char (point-min))
  (forward-line (1- line)))

;; find sticky lines
(defun offside-trap-find-sticky-lines ()
  (let ((line (line-number-at-pos))
	(char (current-column))
	(indent (current-indentation))
	(last-line (count-lines (point-min) (point-max))))
    (let ((min-indent (max char indent)) ; CR: is it really useful ?
	  (cur-line line))
      ; (message (format "min-indent %d" min-indent))
      (save-excursion
	(while
	    (if (= cur-line last-line) nil
	      (offside-trap-goto-line (1+ cur-line))
	      (if (offside-trap-is-sticky-line min-indent)
		  (progn
		    (setq cur-line (+ cur-line 1))
		    t)
		nil)))
	cur-line))))

;; the last char pos of the line
(defun offside-trap-pos-of-end-of-line (line)
  (save-excursion
    (offside-trap-goto-line line)
    (line-end-position)))

;; put the overlay for the sticky lines
(defun offside-trap-overlay-sticky-lines ()
  (interactive)
  (let ((line (line-number-at-pos))
	(sticky-line-end (offside-trap-find-sticky-lines)))
    (move-overlay offside-trap-overlay
		  (point)
		  (offside-trap-pos-of-end-of-line sticky-line-end)
		  (current-buffer))
    (if (= line sticky-line-end)
	(progn
	  ; (delete-overlay offside-trap-overlay)
	  nil)
      (list (1+ line) sticky-line-end))))

;; move the point to the indent head of the line
(defun offside-trap-move-to-indent-head ()
  (let ((indent (current-indentation)))
    (goto-char (+ (line-beginning-position) indent))))

;; push back the last event and exit from the offside trap mode
(defun offside-trap-other-char ()
  (interactive)
  (delete-overlay offside-trap-overlay)
  (setq overriding-terminal-local-map nil)

  ;; push back the keypress
  (setq unread-command-events
	(append (listify-key-sequence(this-command-keys))
		unread-command-events)))

;; offside trap mode keymap (borrowed from isearch.el)
;; offside trap mode keymap (borrowed from isearch.el)
(defvar offside-trap-mode-map
  (let ((keymap (make-sparse-keymap)))
    (define-key keymap (kbd "RET") 'offside-trap-newline-and-block-indent)
    (define-key keymap (kbd "TAB") 'offside-trap-block-indent)
    (define-key keymap (kbd "<C-tab>") 'indent-for-tab-command)
    keymap))

;; offside trap sticky mode keymap (borrowed from isearch.el)
(defvar offside-trap-sticky-mode-map
  (let ((i 0)
	(map (make-keymap)))
    (or (char-table-p (nth 1 map))
	(error "The initialization of offside-trap-sticky-mode-map must be updated"))
    ;; Default binding: exit
    (define-key map [t] 'offside-trap-other-char)
    (define-key map (kbd "TAB") 'offside-trap-block-indent-step)
    (define-key map (kbd "<C-tab>") 'offside-trap-block-indent-step)

    map)
  "Keymap for `offside-trap-sticky-mode'.")

;; current sticky block
;; CR: should be buffer local ?
(setq offside-trap-block nil)

(defun offside-trap-beginning-position-of-line (line)
  (save-excursion
    (offside-trap-goto-line)
    (line-beginning-position)))

(defun offside-trap-end-position-of-line (line)
  (save-excursion
    (offside-trap-goto-line)
    (line-end-position)))

;; point must be at the indent head
;; the sticky lines must be registered in offside-trap-block
(defun offside-trap-block-indent-step-gen (f)
  ; (offside-trap-move-to-indent-head) ; not required. point is at the head already
  ; CR: check offside-trap-block is non nil
  (let ((old-lines (line-number-at-pos))
	(old-chars (current-column)))
    (apply f ())
    (save-excursion
      (let* ((new-lines (line-number-at-pos))
	     (new-chars (current-column))
	     (diff-lines (- new-lines old-lines))
	     (diff-chars (- new-chars old-chars)))
        ; Line might be changed. Rebind offside-trap-block.
	(destructuring-bind (line-start line-end) offside-trap-block
	  (setq offside-trap-block (list (+ diff-lines line-start)
					 (+ diff-lines line-end))))
	(if offside-trap-block
	    (destructuring-bind (line-start line-end) offside-trap-block
	      (indent-rigidly (offside-trap-beginning-position-of-line line-start)
			      (offside-trap-end-position-of-line line-end)
			      diff-chars)))))))

(defun offside-trap-block-indent-step ()
  (interactive)
  (offside-trap-block-indent-step-gen 'indent-for-tab-command))

(defun offside-trap-block-indent ()
  (interactive)
  (offside-trap-move-to-indent-head)
  (setq offside-trap-block (offside-trap-overlay-sticky-lines))
  (setq overriding-terminal-local-map offside-trap-sticky-mode-map)
  (offside-trap-block-indent-step))

(defun offside-trap-newline-and-block-indent ()
  (interactive)
  (setq offside-trap-block (offside-trap-overlay-sticky-lines))
  (setq overriding-terminal-local-map offside-trap-sticky-mode-map)
  (offside-trap-block-indent-step-gen 'haskell-newline-and-indent))

;;;###autoload
(define-minor-mode offside-trap-mode
  "Offside trap mode."
  :lighter " OffsideTrap"
  :keymap offside-trap-mode-map)