;;; vim-insert-mode.el - VIM visual mode.

;; Copyright (C) 2009, 2010 Frank Fischer

;; Author: Frank Fischer <frank.fischer@mathematik.tu-chemnitz.de>,
;;
;; This file is not part of GNU Emacs.

;; TODO:

;;   - when calling a non-vim-mode-command the region should be modified
;;     s.t. the emacs command uses the correct region for block-mode
;;   - check interaction with region (deactivate-mark-hook and others)

;;; Code:

(defcustom vim:visual-region-face vim:default-region-face
  "Face of the highlighted region."
  :type 'face
  :group 'vim-mode)

(vim:define-keymap visual-mode "visual mode" :map-command vmap)

(vim:define-mode visual "VIM visual mode"
                 :ident "V"
                 :keymaps '(vim:visual-mode-keymap
                            vim:operator-pending-mode-keymap
                            vim:motion-mode-keymap)
                 :command-function 'vim:visual-mode-command
                 :cursor 'hollow)
(add-hook 'vim:visual-mode-on-hook 'vim:visual-mode-activate)
(add-hook 'vim:visual-mode-off-hook 'vim:visual-mode-deactivate)

(vim:deflocalvar vim:visual-mode-type 'normal
  "Type of visual mode, should be 'normal, 'linewise or 'block.")

;; Last transient-mark-mode.
(vim:deflocalvar vim:visual-old-transient-mark-mode)

;; Old global variables changed in visual-mode.
(vim:deflocalvar vim:visual-old-global-variables)

;; List of overlays for the current region.
(vim:deflocalvar vim:visual-overlays)

;; The last beginning position of the region.
(vim:deflocalvar vim:visual-last-begin)

;; The last end position of the region.
(vim:deflocalvar vim:visual-last-end)

;; Info-struct to save information for visual-insertion.
(defstruct (vim:visual-insert-info
            (:constructor vim:make-visual-insert-info))
  first-line
  last-line
  column)

;; The last motion used to insert something in visual mode.
(vim:deflocalvar vim:visual-last-insert-info nil)

;; The undo-mark of the last visual mode insert command.
(vim:deflocalvar vim:visual-last-insert-undo nil)

;; If non-nil, the last region will be used when visual mode is activated.
(vim:deflocalvar vim:visual-reactivate-last-region nil)

;;; System variables which must temporarily be buffer local.
(defconst vim:visual-temporary-local-variables
  `(transient-mark-mode
    ,vim:deactivate-region-hook))

;;; Commands that deactivate the mark (and so visual-mode).
(defconst vim:visual-deactivate-mark-commands
  '(clear-rectangle
    clipboard-kill-ring-save
    copy-rectangle
    copy-rectangle-to-register
    kill-rectangle
    open-rectangle
    string-rectangle
    yank-rectangle
    keyboard-quit
    ))


(defun vim:activate-visual (type)
  "Activates visual-mode with certain type."
  (setq vim:visual-mode-type type)
  (if (vim:visual-mode-p)
      (progn
        (vim:visual-highlight-region)
        (let (message-log-max)
          (case vim:visual-mode-type
            ('normal (message "-- VISUAL --"))
            ('linewise (message "-- VISUAL LINE --"))
            ('block (message "-- VISUAL BLOCK --"))
            (t (error "Unknown visual mode type: %s"
                      vim:visual-mode-type)))))
    (vim:activate-visual-mode)))

(defun vim:visual-toggle-mode (type)
  "Switches to visual mode of certain type or deactivates the mode."
  (if (and (vim:visual-mode-p)
           (eq vim:visual-mode-type type)
           (vim:toplevel-execution))
      (vim:visual-mode-exit)
    (vim:activate-visual type)))


(vim:defcmd vim:visual-toggle-normal (nonrepeatable keep-visual)
    "Switches to normal visual-mode or deactivates it."
    (vim:visual-toggle-mode 'normal))


(vim:defcmd vim:visual-toggle-linewise (nonrepeatable keep-visual)
    "Switches to linewise visual-mode or deactivates it."
    (vim:visual-toggle-mode 'linewise))


(vim:defcmd vim:visual-toggle-block (nonrepeatable keep-visual)
    "Switches to block visual-mode or deactivates it."
    (vim:visual-toggle-mode 'block))


(vim:defcmd vim:visual-mode-exit (nonrepeatable)
  "Deactivates visual mode, returning to normal-mode."
  (vim:activate-normal-mode))


(vim:defcmd vim:visual-mode-reactivate (nonrepeatable)
  "Called when the last visual region should be reactivated."
  (unless (and vim:visual-mode-type
               vim:visual-last-begin
               vim:visual-last-end)
    (error "No former visual selection."))

  (setq vim:visual-reactivate-last-region t)
  (vim:activate-visual vim:visual-mode-type))


(defun vim:visual-mode-activate ()
  "Called when visual mode is activated."

  (setq cursor-type vim:visual-mode-cursor)
  
  (if vim:visual-reactivate-last-region
      (progn
        (set-mark (save-excursion
                    (goto-line (car vim:visual-last-begin))
                    (move-to-column (cdr vim:visual-last-begin))
                    (point)))
        (goto-line (car vim:visual-last-end))
        (move-to-column (cdr vim:visual-last-end))
        (setq vim:visual-reactivate-last-region nil))
    (set-mark (point)))
  
  (let (message-log-max)
    (case vim:visual-mode-type
      ('normal (message "-- VISUAL --"))
      ('linewise (message "-- VISUAL LINE --"))
      ('block (message "-- VISUAL BLOCK --"))
      (t (error "Unknown visual mode type: %s"
                vim:visual-mode-type))))

  (setq vim:visual-overlays nil
        vim:visual-old-transient-mark-mode (and (boundp 'transient-mark-mode)
                                                transient-mark-mode)
        vim:visual-old-global-variables
        ;; Remember which system variables weren't buffer local
        (mapcan #'(lambda (variable)
                    (if (assoc variable (buffer-local-variables))
                        (list variable)
                      nil))
                vim:visual-temporary-local-variables))
  
  ;; The make them all buffer local, too.
  (mapc #'make-local-variable vim:visual-temporary-local-variables)
  (when (boundp 'transient-mark-mode) (setq transient-mark-mode nil))
  (add-hook 'post-command-hook 'vim:visual-post-command)
  (add-hook 'pre-command-hook 'vim:visual-normalize-region)
  (add-hook 'post-command-hook 'vim:visual-denormalize-region)
  (add-hook vim:deactivate-region-hook 'vim:visual-mode-exit))


(defun vim:visual-mode-deactivate ()
  "Called when visual mode is deactivated."

  ;; hide the selection
  (vim:visual-hide-region)

  ;; cleanup local variables
  (remove-hook 'pre-command-hook 'vim:visual-normalize-region)
  (remove-hook 'post-command-hook 'vim:visual-denormalize-region)
  (remove-hook 'post-command-hook 'vim:visual-post-command post-command-hook)
  (remove-hook vim:deactivate-region-hook 'vim:visual-mode-exit)
  (when (boundp 'transient-mark-mode)
    (setq transient-mark-mode vim:visual-old-transient-mark-mode))
  (vim:visual-delete-overlays vim:visual-overlays)
  (mapc #'kill-local-variable vim:visual-old-global-variables)
  (vim:deactivate-mark))


(defun vim:visual-mode-command (command)
  "Executes a command in visual mode."
  (case (vim:cmd-type command)
    ('simple (vim:visual-execute-command command))
    ('complex (vim:visual-execute-command command))
    ('special (error "no special so far"))
    (t (vim:visual-execute-motion command))))


(defun vim:visual-execute-command (command)
  "Called to execute a command is visual mode."
  
  ;; save the last region
  (setq vim:visual-last-begin (cons (line-number-at-pos (mark t))
                                    (save-excursion
                                      (goto-char (mark t))
                                      (current-column))))
  (setq vim:visual-last-end (cons (line-number-at-pos (point))
                                  (current-column)))
  (vim:set-mark ?< (save-excursion
                     (goto-line (car vim:visual-last-begin))
                     (move-to-column (cdr vim:visual-last-begin))
                     (point)))
  (vim:set-mark ?> (save-excursion
                     (goto-line (car vim:visual-last-end))
                     (move-to-column (cdr vim:visual-last-end))
                     (point)))
  
  (when (vim:cmd-char-arg-p command)
    (setq vim:current-cmd-arg (read-char-exclusive)))

  (if (vim:cmd-motion-p command)
      (let ((vim:last-undo buffer-undo-list) parameters)
        (push (vim:visual-current-motion) parameters)
        (push :motion parameters)
        (when (vim:cmd-register-p command)
          (push vim:current-register parameters)
          (push :register parameters))
        (when (vim:cmd-char-arg-p command)
          (push vim:current-cmd-arg parameters)
          (push :argument parameters))
        (vim:apply-save-buffer (vim:cmd-function command) parameters)
        (when (vim:cmd-repeatable-p command)
          (setq vim:repeat-events (vconcat vim:current-key-sequence)))
        (vim:connect-undos vim:last-undo)
        (vim:reset-key-state)
        (vim:clear-key-sequence)
        (vim:adjust-point))
    (vim:normal-execute-simple-command command))

  ;; deactivate visual mode unless the command should keep it
  (when (and vim:visual-mode
             (not (vim:cmd-keep-visual-p command)))
    (vim:visual-mode-exit)))
  

(defun vim:visual-execute-motion (command)
  "Called to execute a motion in visual mode."
  
  (setq vim:current-motion command)

  (when current-prefix-arg
    (setq vim:current-motion-count (prefix-numeric-value current-prefix-arg)))

  (when (vim:cmd-arg-p command)
    (setq vim:current-motion-arg (read-char-exclusive)))

  (vim:visual-adjust-region (vim:execute-current-motion))
  (vim:adjust-point)
  (vim:clear-key-sequence)
  (vim:reset-key-state))
  

(defun vim:visual-post-command ()
  (cond
   ((vim:visual-mode-p)
    (if (or (vim:do-deactivate-mark)
            (memq this-command vim:visual-deactivate-mark-commands))
        (condition-case nil
            (vim:visual-mode-exit)
          (error nil))
      (condition-case info
          (vim:visual-highlight-region)
        (error
         (ding)
         (message "visual-mode trouble: %s" info)
         (condition-case nil
             (vim:visual-mode-exit)
           (error nil))))))))


(defun vim:visual-highlight-region ()
  "Highlights the selected region depending on `point' and `mark'.
This function is also responsible for setting the X-selection."

  (let ((start (min (point) (mark t)))
        (end (max (point) (mark t))))
    (case vim:visual-mode-type
      ('normal (vim:visual-highlight-normal start end))
      ('linewise (vim:visual-highlight-linewise start end))
      ('block (vim:visual-highlight-block start end))
      (t (error "Unknown visual mode %s" vim:visual-mode-type))))

  (cond
   ((not (eq window-system 'x)) nil)
   ((= 1 (length vim:visual-overlays))
    (vim:x-set-selection nil (car vim:visual-overlays)))
   ((< 1 (length vim:visual-overlays))
    (let ((text (apply #'concat
                       (mapcar #'(lambda (ov)
                                   (concat (buffer-substring-no-properties (overlay-start ov)
                                                                           (overlay-end ov))
                                           "\n"))
                               vim:visual-overlays))))
      (vim:x-set-selection nil text)))))


(defun vim:visual-highlight-normal (start end)
  "Adjusts the normal region between `start' and `end'."
  (if vim:visual-overlays
      (vim:visual-delete-overlays (cdr vim:visual-overlays))
    (setq vim:visual-overlays nil))
  
  (setq vim:visual-overlays
        (list (vim:visual-create-or-update-overlay (car vim:visual-overlays)
                                                   start (1+ end)))))


(defun vim:visual-highlight-linewise (start end)
  "Adjusts the linewise region between `start' and `end'."
  (let ((start-line (save-excursion
                      (goto-char start)
                      (line-beginning-position)))
        (end-line (save-excursion
                    (goto-char end)
                    (line-end-position))))
    
    (if vim:visual-overlays
        (vim:visual-delete-overlays (cdr vim:visual-overlays))
      (setq vim:visual-overlays nil))
    
    (setq vim:visual-overlays
          (list (vim:visual-create-or-update-overlay (car vim:visual-overlays)
                                                     start-line
                                                     (1+ end-line))))))


(defun vim:visual-highlight-block (start end)
  "Adjusts the block region between `start' and `end'."
  ;; Adapted from: rm-highlight-rectangle
  ;; This function is used to highlight the rectangular region from
  ;; START to END.  We do this by putting an overlay on each line
  ;; within the rectangle.  Each overlay extends across all the
  ;; columns of the rectangle.  We try to reuse overlays where
  ;; possible because this is more efficient and results in less
  ;; flicker.
  (save-excursion
    ;; Calculate the rectangular region represented by point and mark,
    ;; putting start in the north-west corner and end in the
    ;; south-east corner.
    (let ((start-col (save-excursion
		       (goto-char start)
		       (current-column)))
	  (end-col (save-excursion
		     (goto-char end)
		     (current-column))))
      (if (> start-col end-col)
	  (setq start-col (prog1
			      end-col
			    (setq end-col start-col))
		start (save-excursion
			(goto-char start)
			(move-to-column start-col nil)
			(point))
		end (save-excursion
		      (goto-char end)
		      (move-to-column end-col nil)
		      (point))))
      ;; Force a redisplay so we can do reliable window start/end
      ;; calculations.
      (sit-for 0)
      (let* ((old vim:visual-overlays)
	     (new nil)
	     overlay
	     (window-start (max (window-start) start))
	     (window-end (min (window-end) end))
	     (nlines (count-lines window-start
				  (min (1+ window-end)
				       (point-max)))))
	;; Iterate over those lines of the rectangle which are visible
	;; in the currently selected window.
	(goto-char window-start)
	(dotimes (i nlines)
	  (let ((row-start (progn
			     (move-to-column start-col nil)
			     (point)))
		(row-end (progn
			   (move-to-column end-col nil)
			   (min (1+ (point))
				(line-end-position)))))
	    ;; Trim old leading overlays.
	    (while (and old
			(setq overlay (car old))
			(< (overlay-start overlay) row-start)
			(/= (overlay-end overlay) row-end))
	      (delete-overlay overlay)
	      (setq old (cdr old)))
	    ;; Reuse an overlay if possible, otherwise create one.
	    (if (and old
		     (setq overlay (car old))
		     (or (= (overlay-start overlay) row-start)
			 (= (overlay-end overlay) row-end)))
		(progn
		  (move-overlay overlay row-start row-end)
		  (setq new (cons overlay new)
			old (cdr old)))
	      (setq overlay (make-overlay row-start row-end))
	      (overlay-put overlay 'face vim:visual-region-face)
	      (overlay-put overlay 'priority 99)
	      (setq new (cons overlay new))))
	  (forward-line 1))
	;; Trim old trailing overlays.
	(vim:visual-delete-overlays old)
	(setq vim:visual-overlays (nreverse new))))))


(defun vim:visual-create-or-update-overlay (ov start end)
  "Creates a new overlay or updates the given overlay."
  (if (overlayp ov)
      (progn
        (vim:visual-delete-overlays (cdr vim:visual-overlays))
        (setcdr vim:visual-overlays nil)
        (move-overlay (car vim:visual-overlays) start  end)
        ov)
    (let ((ov (make-overlay start  end)))
      (vim:visual-hide-region)
      (overlay-put ov 'face vim:visual-region-face)
      (overlay-put ov 'priority 99)
      ov)))
    
  

(defun vim:visual-hide-region ()
  "Removes the highlighting."
  (vim:visual-delete-overlays vim:visual-overlays)
  (setq vim:visual-overlays nil))

(defun vim:visual-delete-overlays (overlays)
  "Deletes all overlays in `overlays'."
  (mapcar #'delete-overlay overlays))

(defun vim:visual-current-motion ()
  "Returns a motion representing the current region."
  (case vim:visual-mode-type
    ('normal (vim:visual-current-normal-motion))
    ('linewise (vim:visual-current-linewise-motion))
    ('block (vim:visual-current-block-motion))))

(defun vim:visual-current-normal-motion ()
  "Returns a motion representing the current normal region."
  (vim:make-motion :has-begin t
                   :begin (min (point) (mark t))
                   :end (max (point) (mark t))
                   :type 'inclusive))


(defun vim:visual-current-linewise-motion ()
  "Returns a motion representing the current linewise region."
  (vim:make-motion :has-begin t
                   :begin (min (point) (mark t))
                   :end (max (point) (mark t))
                   :type 'linewise))


(defun vim:visual-current-block-motion ()
  "Returns a motion representing the current block region."
  (vim:make-motion :has-begin t
                   :begin (min (point) (mark t))
		   :end (max (point) (mark t))
		   :type 'block))


(defun vim:visual-adjust-region (motion)
  "Adjusts the region according to a certain motion."
  (if (vim:motion-has-begin motion)
      (progn
        (case (vim:motion-type motion)
          ('linewise (vim:activate-visual 'linewise))
          ('block (vim:activate-visual 'block))
          (t (vim:activate-visual 'normal)))
        (if (< (point) (mark t))
            ;; increase backward
            (progn
              (goto-char (vim:motion-begin-pos motion))
              (when (> (vim:motion-end-pos motion) (mark t))
                (set-mark (vim:motion-end-pos motion))))
          ;; increase forward
          (when (< (vim:motion-begin motion)
                          (mark t))
            (set-mark (vim:motion-begin motion)))))))
      

(vim:defcmd vim:visual-insert (motion)
  "Starts insertion at the left column of a visual region."

  (vim:visual-start-insert 
   (vim:make-visual-insert-info :first-line (vim:motion-first-line motion)
                                :last-line (vim:motion-last-line motion)
                                :column (vim:motion-first-col motion))))

(defun vim:visual-start-insert (insert-info)
  "Starts a new multi-line insert operation with `insert-info'."
  
  (setq vim:visual-last-insert-info insert-info)
  (goto-line (vim:visual-insert-info-first-line insert-info))
  (move-to-column (vim:visual-insert-info-column insert-info) t)
  
  (case vim:visual-mode-type
    ('block
     ;; TODO: ensure the right command is run on repetition.
     ;; this is really a dirty hack
     (setq vim:current-key-sequence "i")
     (vim:cmd-insert :count 1)
     (add-hook 'vim:normal-mode-on-hook 'vim:insert-block-copies))

    ('linewise
     ;; TODO: ensure the right command is run on repetition.
     ;; this is really a dirty hack
     (setq vim:current-key-sequence "I")
     (vim:cmd-Insert :count 1)
     (add-hook 'vim:normal-mode-on-hook 'vim:insert-linewise-copies)))

  (setq vim:visual-last-insert-undo vim:last-insert-undo))

(defun vim:insert-block-copies ()
  "Called to repeat the last block-insert."
  (remove-hook 'vim:normal-mode-on-hook 'vim:insert-block-copies)
  (let ((begrow (vim:visual-insert-info-first-line vim:visual-last-insert-info))
        (begcol (vim:visual-insert-info-column vim:visual-last-insert-info))
        (endrow (vim:visual-insert-info-last-line vim:visual-last-insert-info)))
    (save-excursion
      (dotimes (i (- endrow begrow))
            (goto-line (+ begrow i 1))
        (when (>= (save-excursion
                    (end-of-line)
                    (current-column))
                  begcol)
          (move-to-column begcol t)
          (vim:cmd-repeat)))
      (setq vim:last-undo vim:visual-last-insert-undo))))


(defun vim:insert-linewise-copies ()
  "Called to repeat the last linewise-insert."
  (remove-hook 'vim:normal-mode-on-hook 'vim:insert-linewise-copies)
  (let ((begrow (vim:visual-insert-info-first-line vim:visual-last-insert-info))
        (endrow (vim:visual-insert-info-last-line vim:visual-last-insert-info)))
    (save-excursion
      (goto-line (1+ begrow))
      (dotimes (i (- endrow begrow))
        (goto-line (+ begrow i 1))
        (vim:cmd-repeat))
      (setq vim:last-undo vim:visual-last-insert-undo))))


(vim:defcmd vim:visual-append (motion)
  "Starts insertion at the right column of a visual block."
  
  (vim:visual-start-append
   (vim:make-visual-insert-info :first-line (vim:motion-first-line motion)
                                :last-line (vim:motion-last-line motion)
                                :column (vim:motion-last-col motion))))

  
(defun vim:visual-start-append (insert-info)
  "Starts a new multi-line append operation with `insert-info'."
  
  (setq vim:visual-last-insert-info insert-info)
  (goto-line (vim:visual-insert-info-first-line insert-info))
  (move-to-column (vim:visual-insert-info-column insert-info) t)
  
  (case vim:visual-mode-type
    ('block
     ;; TODO: ensure the right command is run on repeation.
     ;; this is really a dirty hack
     (setq vim:current-key-sequence "a")
     (vim:cmd-append :count 1)
     (add-hook 'vim:normal-mode-on-hook 'vim:append-block-copies))

    ('linewise
     ;; TODO: ensure the right command is run on repeation.
     ;; this is really a dirty hack
     (setq vim:current-key-sequence "A")
     (vim:cmd-Append :count 1)
     (add-hook 'vim:normal-mode-on-hook 'vim:insert-linewise-copies)))

  (setq vim:visual-last-insert-undo vim:last-insert-undo))


(defun vim:append-block-copies ()
  "Called to repeat the last block-insert."
  (remove-hook 'vim:normal-mode-on-hook 'vim:append-block-copies)
  (let ((begrow (vim:visual-insert-info-first-line vim:visual-last-insert-info))
        (endcol (vim:visual-insert-info-column vim:visual-last-insert-info))
        (endrow (vim:visual-insert-info-last-line vim:visual-last-insert-info)))
    (save-excursion
      (goto-line (1+ begrow))
      (dotimes (i (- endrow begrow))
        (move-to-column (1+ endcol) t) ; extend the newline at the end
        (move-to-column endcol t)
        (vim:cmd-repeat)
        (forward-line 1))
      (setq vim:last-undo vim:visual-last-insert-undo))))


(vim:defcmd vim:visual-exchange-point-and-mark (nonrepeatable keep-visual)
   "Exchanges point and mark."
   (exchange-point-and-mark))


(vim:defcmd vim:visual-jump-point (nonrepeatable keep-visual)
  "In normal and linewise visual mode, this is the same as
`vim:visual-exchange-point-and-mark'.  In block visual-mode the
cursor jumps to the other corner of the selected region in the
current line."
  (case vim:visual-mode-type
    ((normal linewise)
     (vim:visual-exchange-point-and-mark))
    ('block
	(let ((mark-col (save-excursion
			  (goto-char (mark t))
			  (current-column)))
	      (point-col (current-column)))
	  (set-mark (save-excursion
		      (goto-char (mark t))
		      (move-to-column point-col t)
		      (point)))
	  (move-to-column mark-col t)))
    (t (error "Not in visual mode."))))


(vim:defcmd vim:visual-ex-read-command (nonrepeatable)
  "Starts ex-mode with visual-marks as initial input."
  (vim:ex-read-command "'<,'>"))

(vim:deflocalvar vim:visual-last-point nil
  "The position of point before a region-command.")
(vim:deflocalvar vim:visual-last-mark nil
  "The position of mark before a region-command.")
(vim:deflocalvar vim:visual-new-point nil
  "The position of modified point before a region-command." )

(defun vim:visual-normalize-region ()
  (when (vim:visual-mode-p)
    (let* ((iform (interactive-form this-command))
           (use-region (or (eq this-command 'execute-extended-command)
                           (and iform (cdr iform)
                                (string= (cadr iform) "r")))))
      (when use-region
        (setq vim:visual-last-point (point)
              vim:visual-last-mark (mark))
        (case vim:visual-mode-type
          (normal
           (if (> (point) (mark)) (forward-char) (set-mark (1+ (mark)))))
          
          (linewise
           (if (> (point) (mark))
               (progn
                 (end-of-line)
                 (forward-char)
                 (set-mark (save-excursion
                             (goto-char (mark))
                             (line-beginning-position))))
             (beginning-of-line)
             (set-mark (save-excursion
                         (goto-char (mark))
                         (1+ (line-end-position))))))
          
          ('block))
        (setq vim:visual-new-point (point))))))

(defun vim:visual-denormalize-region()
  (when (and vim:visual-last-point vim:visual-last-mark)
    (set-mark vim:visual-last-mark)
    (when (= (point) vim:visual-new-point)
      (goto-char vim:visual-last-point))
    (setq vim:visual-last-point nil
          vim:visual-last-mark nil
          vim:visual-new-point nil)))


(defun vim:visual-mouse-clicked (event)
  "Dispatches singe, double, triple or quad clicks."
  (interactive "e")
  (case (vim:mouse-click-count event)
    (2 (vim:visual-drag-mouse-region event 'word))
    (3 (vim:visual-drag-mouse-region event 'linewise))
    (4 (vim:visual-drag-mouse-region event 'block))
    (t (vim:visual-drag-mouse-region event 'char))))

(defun vim:visual-drag-mouse-region (event mode)  
  "Update visual-region during mouse-motion."
  (vim:visual-mode-exit)
  (select-window (vim:mouse-event-window event))
  (let ((start-pos (vim:mouse-event-point event)))
    (when start-pos
      (goto-char start-pos)
    
      (case mode
        ('word
         (vim:visual-toggle-normal)
         (multiple-value-bind (p m) (vim:visual-get-word-region start-pos start-pos)
           (set-mark m)
           (goto-char p))
         (vim:visual-highlight-region))
        ('linewise
         (vim:visual-toggle-linewise)
         (vim:visual-highlight-region))
        ('block
         (vim:visual-toggle-block)
         (vim:visual-highlight-region)))
    
      (vim:track-mouse
        (catch 'exit
          (while t
            (let ((ev (vim:read-event)))
              (cond
               ((vim:mouse-movement-p ev)
                (let ((end-pos (vim:mouse-event-point ev)))
                  (when end-pos
                    (when (and (not (vim:visual-mode-p))
                               (/= start-pos end-pos))
                      (if (eq mode 'linewise)
                          (vim:visual-toggle-linewise)
                        (vim:visual-toggle-normal)))

                    (when (vim:visual-mode-p)
                      (if (eq mode 'word)
                          (multiple-value-bind (p m) (vim:visual-get-word-region end-pos start-pos)
                            (set-mark m)
                            (goto-char p))
                        (set-mark start-pos)
                        (goto-char end-pos))
              
                      (vim:visual-highlight-region)))))
           
               ((vim:mouse-event-p ev) (throw 'exit nil))

               (t
                (push ev unread-command-events)
                (let* ((keyseq (read-key-sequence nil))
                       (cmd (key-binding keyseq)))
                  (call-interactively cmd)
                  (unless (vim:visual-mode-p)
                    (throw 'exit nil))))))))))))


(defun vim:visual-get-word-region (point-pos mark-pos)
  "Returns the start and end position of the word-wise region."
  (let ((point-motion (save-excursion (goto-char point-pos) (vim:motion-inner-word)))
        (mark-motion (save-excursion (goto-char mark-pos) (vim:motion-inner-word))))
    (if (< point-pos mark-pos)
        (values (vim:motion-begin point-motion)
                (vim:motion-end mark-motion))
      (values (vim:motion-end point-motion)
              (vim:motion-begin mark-motion)))))
                      

(provide 'vim-visual-mode)

;;; vim-visual-mode.el ends here
