Subject: Re: bug#8439: [PATCH] ffap.el -- detect paths with spaces (v3)

From: jari <[email-masked]>
Date: Sat, 20 Oct 2012 10:56:01 +0300

On 2012-10-19 21:44, Stefan Monnier wrote:
| A few comments about your patch below.

Included. I also worked on Eli's ideas about detecting "/" or "\" near
point.

The patch has increased significantly as there are no easy way to deal
with spaces. For test cases I've used, it looks promising.

Here is also ffap.el with the patch included for "live" testing.

Jari
>From e0bf484d6dd08344b53d285614300ccd42e012dc Mon Sep 17 00:00:00 2001
From: Jari Aalto <[email-masked]>
Date: Sat, 20 Oct 2012 10:43:35 +0300
Subject: [PATCH] lisp/ffap.el: Detect patchs with spaces.
Organization: Private
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 8bit

2012-10-19 Fri  Jari Aalto  <[email-masked]>

        * ffap.el (ffap-file-name-with-spaces-flag): New variable.
        (ffap-cygwin-convert): New function.
        (ffap-search-forward-file-end): New function.
        (ffap-search-backward-file-end): New function.
        (ffap-path-separator-near-point): New function.
        (ffap-string-at-point): If `ffap-file-name-with-spaces-flag' is set,
        allow spaces in file names.

Signed-off-by: Jari Aalto <[email-masked]>
---
 lisp/ffap.el |  186 ++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 167 insertions(+), 19 deletions(-)

diff --git a/lisp/ffap.el b/lisp/ffap.el
index 4c75609..3195a1a 100644
--- a/lisp/ffap.el
+++ b/lisp/ffap.el
@@ -1036,6 +1036,135 @@ possibly a major-mode name, or one of the symbol
   ;; Added at suggestion of RHOGEE (for ff-paths), 7/24/95.
   "Last string returned by `ffap-string-at-point'.")
 
+;; Test cases: (let ((ffap-file-name-with-spaces-flag t)) (ffap-string-at-point))
+;;
+;; c:/Program Files/Open Text Evaluation Media/Open Text Exceed 14 x86/Program here.txt
+;; c:/Program Files/Open Text Evaluation Media/Open Text Exceed 14 x86/Program Files/Hummingbird/
+;; c:\Program Files\Open Text Evaluation Media\Open Text Exceed 14 x86\Program Files\Hummingbird\
+;; c:\Program Files\Freescale\CW for MPC55xx and MPC56xx 2.10\PowerPC_EABI_Tools\Command_Line_Tools\CLT_Usage_Notes.txt
+;; C:\temp\program.log on Windows or /var/log/program.log on Unix.
+
+(defvar ffap-file-name-with-spaces-flag (memq system-type '(ms-dos windows-nt))
+  "If non-nil, enable looking for paths with spaces in `ffap-string-at-point'.
+Enabled in W32 by default.")
+
+(defun ffap-cygwin-path (path)
+  "Convert \"<drive>:\" into \"/cygdrive/<drive>\"."
+  (if (string-match "^\\([a-zA-Z]\\):[/\\\\]\\(.*\\)" path)
+      (let ((drive (downcase (match-string 1 path)))
+	    (str (match-string 2 path)))
+	(format "/cygdrive/%s/%s"
+		drive
+		(replace-regexp-in-string "[\\\\]" "/" str)))
+    path))
+
+(defun ffap-cygwin-convert (path)
+  "If in Cygwin, convert \"<drive>:\" to \"/cygdrive/<drive>\"."
+  (if (eq system-type 'cygwin)
+      (ffap-cygwin-path path)
+    path))
+
+(defun ffap-search-backward-file-end (&optional path-separator end)
+  "Search backward position point where file would probably end.
+Optional PATH-SEPARATOR defaults to \"/\". The search maximum is
+`line-end-position' or optional END point.
+
+Suppose the cursor is somewhere that might be near end of file,
+the guessing would position point before punctuation (like comma)
+after the file extension:
+
+  C:\temp\file.log, which contain ....
+  =============================== (before)
+  ---------------- (after)
+
+
+  C:\temp\file.log on Windows or /tmp/file.log on Unix
+  =============================== (before)
+  ---------------- (after)
+
+The strategy is to search backward until PATH-SEPARATOR which defaults to
+\"/\" and then take educated guesses.
+
+Move point and return point if an adjustment was done."
+  (or path-separator
+      (setq path-separator "/"))
+  (let (point
+	puct
+	end
+	whitespace-p)
+    (when (re-search-backward
+	   (regexp-quote path-separator) (line-beginning-position) t)
+      (forward-char 1)			 ;at the very beginning of match
+      (when (looking-at ".*?\\([][<>()\"'`,.:;]\\)") ;until typical punctuation
+	(setq end (match-end 0))
+	(setq punct (match-string 1))
+	(setq whitespace-p (looking-at "[ \t\r\n]\\|$"))
+	(goto-char end)
+	(cond
+	 ((and (string-equal punct ".")
+	       whitespace-p)		     ;end of sentence
+	  (setq point (1- (point))))
+	 ((and (string-equal punct ".")
+	       (looking-at "[a-zA-Z0-9.]+")) ;possibly file extension
+	  (setq point (match-end 0)))
+	 (t
+	  (setq point (point)))))
+      (when point
+	(goto-char point)
+	point))))
+
+(defun ffap-search-forward-file-end (&optional path-separator)
+  "Search PATH-SEPARATOR and position point at file's maximum ending (including spaces).
+Optional PATH-SEPARATOR defaults to \"/\".
+Call `ffap-search-backward-file-end' to refine the ending point."
+  (or path-separator
+      (setq path-separator "/"))
+  (let* ((chars  ;expected chars in file name
+	  (concat "[^][^<>()\"'`;,#*|"
+		  ;; exclude the opposite as we know the separator
+		  (if (string-equal path-separator "/")
+		      "\\\\"
+		    "/")
+		  "\t\r\n]"))
+	 (re (concat
+	      chars "*"
+	      (if path-separator
+		  (regexp-quote path-separator)
+		"/")
+	      chars "*")))
+    (when (looking-at re)
+      (goto-char (match-end 0)))))
+
+(defun ffap-path-separator-near-point ()
+  "Search backward and forward for closest slash or backlash in line.
+Return string slash or backslash. Point is moved to closest position."
+  (let ((point (point))
+	str
+	pos)
+    (if (looking-at ".*?/")
+	(setq str "/"
+	      pos (match-end 0)))
+    (if (looking-at ".*?\\\\")
+	(if (or (null pos)
+		(< (match-end 0) pos))
+	    (setq str "\\"
+		  pos (match-end 0))))
+    (goto-char point)
+    (if (re-search-backward "/" (line-beginning-position) t)
+	(if (or (null pos)
+		(< (- point (point)) (- pos point)))
+	    (setq str "/"
+		  pos (1+ (point)))))	;1+ to keep cursor at the end of char
+    (goto-char point)
+    (if (re-search-backward "\\\\" (line-beginning-position) t)
+	(if (or (null pos)
+		(< (- point (point)) (- pos point)))
+	    (setq str "\\"
+		  pos (1+ (point)))))
+    (if pos
+	(goto-char pos))
+    str))
+
 (defun ffap-string-at-point (&optional mode)
   "Return a string of characters from around point.
 MODE (defaults to value of `major-mode') is a symbol used to look up string
@@ -1043,26 +1172,45 @@ syntax parameters in `ffap-string-at-point-mode-alist'.
 If MODE is not found, we use `file' instead of MODE.
 If the region is active, return a string from the region.
 Sets `ffap-string-at-point' and `ffap-string-at-point-region'."
-  (let* ((args
-	  (cdr
-	   (or (assq (or mode major-mode) ffap-string-at-point-mode-alist)
-	       (assq 'file ffap-string-at-point-mode-alist))))
-	 (pt (point))
-	 (str
-	  (if (and transient-mark-mode mark-active)
-	      (buffer-substring
-	       (setcar ffap-string-at-point-region (region-beginning))
-	       (setcar (cdr ffap-string-at-point-region) (region-end)))
-	    (buffer-substring
-	     (save-excursion
-	       (skip-chars-backward (car args))
-	       (skip-chars-forward (nth 1 args) pt)
-	       (setcar ffap-string-at-point-region (point)))
-	     (save-excursion
-	       (skip-chars-forward (car args))
-	       (skip-chars-backward (nth 2 args) pt)
-	       (setcar (cdr ffap-string-at-point-region) (point)))))))
+  (let* ((cygwin-p (string-match "cygwin" (emacs-version)))
+	 path-separator
+	 beg
+	 end
+	 (args
+          (cdr
+           (or (assq (or mode major-mode) ffap-string-at-point-mode-alist)
+               (assq 'file ffap-string-at-point-mode-alist))))
+         (pt (point))
+         (str
+          (if (and transient-mark-mode mark-active)
+              (buffer-substring
+               (setcar ffap-string-at-point-region (region-beginning))
+               (setcar (cdr ffap-string-at-point-region) (region-end)))
+            (buffer-substring
+             (save-excursion
+	       (if (and ffap-file-name-with-spaces-flag
+			(memq mode '(nil file)))
+		   (when (setq path-separator (ffap-path-separator-near-point))
+		     (while (re-search-backward
+			     (regexp-quote path-separator)
+			     (line-beginning-position) t)
+		       (goto-char (match-beginning 0)))))
+               (skip-chars-backward (car args))
+               (skip-chars-forward (nth 1 args) pt)
+               (setcar ffap-string-at-point-region (point))
+	       (setq beg (point)))
+             (save-excursion
+               (skip-chars-forward (car args))
+               (skip-chars-backward (nth 2 args) pt)
+	       (setq end (point))
+	       (when (and ffap-file-name-with-spaces-flag
+			  (memq mode '(nil file)))
+		 (ffap-search-forward-file-end path-separator)
+		 (ffap-search-backward-file-end path-separator)
+		 (setq end (point)))
+               (setcar (cdr ffap-string-at-point-region) end))))))
     (set-text-properties 0 (length str) nil str)
+    (setq str (ffap-cygwin-convert str))
     (setq ffap-string-at-point str)))
 
 (defun ffap-string-around ()
-- 
1.7.10.4