为什么我的函数替换字符出错?

我写了一个小函数帮助我替换字符。 我想达到的效果是把

something sl a link sl something

转换成

something [[a link][a link]] something

也就是说把sl之间的字符替换到[[%s][%s]]的效果。

我的实现是这样的


(defvar short-finger-replace-book-for-org '(("sl" "sl" "[[%s][%s]]"))
  "a list of cons of (key . replacement) for replacing in org mode.

In replacement, use `format' compatible %-sequnce regarding surrounded text
as substitute.

For example (\"sc\" . \"```%s```\") replace
\" sc some code sc\" into \"```some code```\".")

;; something sl some sl something

(setq short-finger-replace-book-for-org '(("sl" "sl" " [[%s][%s]] ")))

(defvar short-finger-active-book ()
  "Current used short-finger-replace-book.")

(setq short-finger-active-book short-finger-replace-book-for-org)

(defun short-finger-format-line ()
  (interactive)
  (dolist (key-replace-con short-finger-active-book)
    (let ((match-str ; the pattern we are going to replace
           (format "[ \n]%s .+? %s[ \n]"
                   (nth 0 key-replace-con)
                   (nth 1 key-replace-con))))
      (move-beginning-of-line nil) ; to search whole line
      (while (re-search-forward match-str (line-end-position) t)
        (message (format "%d" (point)))
        (message (match-string-no-properties 0))
        (replace-match
         (multi-format ; replace match-string with replace string :P
          (nth 2 key-replace-con)
          (short-finger--extract-middle (match-string-no-properties 0) key-replace-con))
         nil t)))))

(defun test-replace ()
  (interactive)
  (re-search-forward "[ \n]sl .+? sl[ \n]" (line-end-position))
  (replace-match " [[some][some]] "))


(defun short-finger--extract-middle (string key-replace-con)
  (let ((front-regex (format "^[ \n]%s " (car key-replace-con)))
        (end-regex (format " %s[ \n]$" (nth 1 key-replace-con))))
    (replace-regexp-in-string
     front-regex ""
     (replace-regexp-in-string
      end-regex "" string))))

(defun multi-format (string replacement)
  "Replace one string multiple times.

E.g:
(multi-format \"something %s %s\" \"haha\")
=> \"something haha haha\""
  (let ((element-list (split-string string "%s"))
        (return-list ()))
    (dolist (element element-list)
      (setq return-list (append return-list (list element replacement))))
    (setq return-list (nbutlast return-list))
    (apply #'concat return-list)))

然而运行函数是这样的

演示

(如果gif链接失效的话,情况基本就是我在下面那行注释上运行函数,函数在第三行莫名插入替换字符)

这是什么鬼啊?

我测试过了所有的子函数,测试函数test-replace也没问题,edebug一步一步走会在replace-match莫名跳到第三行……

求大神指教


谢谢大家 :smile: :heart:

问题的直接原因似乎是没有使用save-match-data@twlz0ne 给出了更好地实现,避免了很多问题。

貌似split-string 没有用 save-match-data 会改变 match 的位置

2 个赞

你可能是该了函数代码之后忘记 C-x C-e,所以出现截图重的结果 [[some][some]]

不过你的代码确实存在问题,replace-match 之后,光标位置变化了,导致 while 错乱。

测试 1


(with-temp-buffer
  (insert "\
;;
;;

;; something sl some sl something")
  (short-finger-format-line)
  (print (buffer-string)))

结果:

;;
;;
 [[ [][ []] [
;][
;]] ; something sl some sl something

测试 2


(with-temp-buffer
  (insert "\
;;
;;
;;
;; something sl some sl something")
  (short-finger-format-line)
  (print (buffer-string)))

结果:

;;
;;
 [[;;][;;]] 
;; something sl some sl something

测试 3


(with-temp-buffer
  (insert "\
;;
;;
;; something sl some sl something")
  (short-finger-format-line)
  (print (buffer-string)))

结果:

死循环
2 个赞

正解。

(defun multi-format (string replacement)
  "Replace one string multiple times.

E.g:
(multi-format \"something %s %s\" \"haha\")
=> \"something haha haha\""
  (save-match-data
(let ((element-list (split-string string "%s"))
      (return-list ()))
  (dolist (element element-list)
    (setq return-list (append return-list (list element replacement))))
  (setq return-list (nbutlast return-list))
  (apply #'concat return-list))))

这樣就正常些了。不過似乎还有別的问題。

1 个赞

不太明白你的需求,你实际遇到的问题是什么?

我感觉你把问题搞复杂了,使用分组捕获,不需要 (short-finger--extract-middle ...) 正则嵌套正则;填充结果不必 (multi-format ...) 也就没有 save-match-data 的问题:

(setq short-finger-replace-book-for-org '(("sl" "sl" " [[%s][%s]] ")))
(setq short-finger-active-book short-finger-replace-book-for-org)

(defun short-finger-format-line ()
  (interactive)
  (dolist (key-replace-con short-finger-active-book)
    (let ((match-str ; the pattern we are going to replace
           (format "[ \n\t]%s \\(.+?\\) %s[ \n\t]"
                   (nth 0 key-replace-con)
                   (nth 1 key-replace-con))))
      (move-beginning-of-line nil) ; to search whole line
      (while (re-search-forward match-str (line-end-position) t)
        (replace-match
         (replace-regexp-in-string
          "\\%s"
          (match-string-no-properties 1)
          (nth 2 key-replace-con))
         nil t)
        ))))

测试:

(with-temp-buffer
  (insert "\
;;
;;
;; something sl foo sl something ;; something sl bar sl something")
  (short-finger-format-line)
  (print (buffer-string)))

结果:

;;
;;
;; something [[foo][foo]] something ;; something [[bar][bar]] something
1 个赞

org-mode里想要导出的文件内部链接正常就得在方括号里输入两遍链接目标,比如 [[target][target]],还有就是mardown里反引号有点远不想去伸手去按。

有这么一个函数就可以用很好按的字符扩展成这些语法。

我都不知道还能这么用 :smile:

我看文档的时候,一直以为里面说的parentheses是指真的小括号,这个功能是方便抓取lisp代码里的结构的……

你这是先写出来然后变换格式,一般这种需求用snippet比较多,输入olink然后yas-expand变成[[|][|]]然后依次跳到两个|处输入。

也是…有点重复造轮子