summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Goaziou <mail@nicolasgoaziou.fr>2019-03-09 10:58:41 +0100
committerNicolas Goaziou <mail@nicolasgoaziou.fr>2019-03-10 18:00:36 +0100
commit2b00d6281606ef295dadbe4ddd929ebdd6716d60 (patch)
tree9cee65fa0460fae32d7fbacf136d890c7cb7e594
parent3318b50a397d0dfe23b5237246fdde39fe1e5881 (diff)
downloadorg-mode-2b00d6281606ef295dadbe4ddd929ebdd6716d60.tar.gz
Change bracket link escape syntax
* contrib/lisp/org-link-edit.el (org-link-edit--link-data): * lisp/ob-tangle.el (org-babel-tangle-comment-links): Update match-group. (org-babel-detangle): Remove unnecessary `org-link-escape' call. (org-babel-tangle-jump-to-org): Update match group. (org-link-url-hexify): (org-link-escape-chars): Remove variables. * lisp/ol.el (org-link--decode-compound): Renamed from `org-link--unescape-compound'. (org-link--decode-single-byte-sequence): Renamed from `org-link--unescape-single-byte-sequence'. (org-link-make-regexps): Update `org-link-bracket-re' syntax. (org-link-encode): New function, renamed from `org-link-escape'. (org-link-decode): New function, renamed from `org-link-unescape'. (org-link-escape): (org-link-unescape): Use new escape syntax. (org-link-make-string): Apply new escaping rules. (org-link-display-format): (org-insert-link): Update match group. * lisp/org-agenda.el (org-diary): (org-agenda-format-item): (org-agenda-to-appt): Update match group. * lisp/org-clock.el (org-clocktable-write-default): Update match group. * lisp/org-element.el (org-element-link-parser): Update match group. * lisp/org-mobile.el (org-mobile-escape-olp): (org-mobile-locate-entry): Apply function renaming. * lisp/org-protocol.el (org-protocol-split-data): (org-protocol-parse-parameters): Apply function renaming. * lisp/org.el (org-refile): Update match group. * testing/README (Interactive testing from within Emacs): Fix examples. * testing/lisp/test-ol.el (test-ol/encode): Merge old escape tests. (test-ol/decode): Merge old unescape tests. (test-ol/escape): (test-ol/unescape): (test-ol/make-string): New tests. * testing/lisp/test-org-clock.el (test-org-clock/clocktable/link): * testing/lisp/test-org.el (test-org/custom-id): (test-org/fuzzy-links): * testing/lisp/test-ox.el (test-org-export/resolve-fuzzy-link): Update tests.
-rw-r--r--contrib/lisp/org-link-edit.el4
-rw-r--r--lisp/ob-tangle.el18
-rw-r--r--lisp/ol.el147
-rw-r--r--lisp/org-agenda.el8
-rw-r--r--lisp/org-clock.el4
-rw-r--r--lisp/org-compat.el3
-rw-r--r--lisp/org-element.el4
-rw-r--r--lisp/org-mobile.el17
-rw-r--r--lisp/org-protocol.el4
-rw-r--r--lisp/org.el2
-rw-r--r--testing/README8
-rw-r--r--testing/lisp/test-ol.el158
-rw-r--r--testing/lisp/test-org-clock.el36
-rw-r--r--testing/lisp/test-org.el10
-rw-r--r--testing/lisp/test-ox.el4
15 files changed, 221 insertions, 206 deletions
diff --git a/contrib/lisp/org-link-edit.el b/contrib/lisp/org-link-edit.el
index 9059df0..b046a77 100644
--- a/contrib/lisp/org-link-edit.el
+++ b/contrib/lisp/org-link-edit.el
@@ -95,9 +95,7 @@ The list includes
(match-end 0)
(save-match-data
(org-link-unescape (match-string-no-properties 1)))
- (or (and (match-end 3)
- (match-string-no-properties 3))
- "")))
+ (or (match-string-no-properties 2) "")))
((looking-at org-plain-link-re)
(list (match-beginning 0)
(match-end 0)
diff --git a/lisp/ob-tangle.el b/lisp/ob-tangle.el
index dcdbc68..f9f7859 100644
--- a/lisp/ob-tangle.el
+++ b/lisp/ob-tangle.el
@@ -30,6 +30,7 @@
(require 'cl-lib)
(require 'org-src)
(require 'org-macs)
+(require 'ol)
(declare-function make-directory "files" (dir &optional parents))
(declare-function org-at-heading-p "org" (&optional ignored))
@@ -40,14 +41,8 @@
(declare-function org-element-type "org-element" (element))
(declare-function org-heading-components "org" ())
(declare-function org-in-commented-heading-p "org" (&optional no-inheritance))
-(declare-function org-link-escape "org" (text &optional table merge))
-(declare-function org-link-open-from-string "ol" (s &optional arg))
-(declare-function org-link-trim-scheme "ol" (uri))
-(declare-function org-store-link "org" (arg &optional interactive?))
(declare-function outline-previous-heading "outline" ())
-(defvar org-link-types-re)
-
(defcustom org-babel-tangle-lang-exts
'(("emacs-lisp" . "el")
("elisp" . "el"))
@@ -330,8 +325,6 @@ references."
(delete-region (save-excursion (beginning-of-line 1) (point))
(save-excursion (end-of-line 1) (forward-char 1) (point)))))
-(defvar org-stored-links)
-(defvar org-link-bracket-re)
(defun org-babel-spec-to-string (spec)
"Insert SPEC into the current file.
@@ -506,10 +499,7 @@ non-nil, return the full association list to be used by
`(("start-line" . ,(number-to-string
(org-babel-where-is-src-block-head)))
("file" . ,(buffer-file-name))
- ("link" . ,(org-link-escape
- (progn
- (call-interactively #'org-store-link)
- (org-no-properties (car (pop org-stored-links))))))
+ ("link" . ,(org-no-properties (org-store-link nil)))
("source-name" .
,(nth 4 (or info (org-babel-get-src-block-info 'light)))))))
(list (org-fill-template org-babel-tangle-comment-format-beg link-data)
@@ -527,7 +517,7 @@ which enable the original code blocks to be found."
(let ((counter 0) new-body end)
(while (re-search-forward org-link-bracket-re nil t)
(when (re-search-forward
- (concat " " (regexp-quote (match-string 3)) " ends here"))
+ (concat " " (regexp-quote (match-string 2)) " ends here"))
(setq end (match-end 0))
(forward-line -1)
(save-excursion
@@ -549,7 +539,7 @@ which enable the original code blocks to be found."
(and (setq start (line-beginning-position))
(setq body-start (line-beginning-position 2))
(setq link (match-string 0))
- (setq block-name (match-string 3))
+ (setq block-name (match-string 2))
(save-excursion
(save-match-data
(re-search-forward
diff --git a/lisp/ol.el b/lisp/ol.el
index 4623153..6f33771 100644
--- a/lisp/ol.el
+++ b/lisp/ol.el
@@ -375,13 +375,6 @@ single keystroke rather than having to type \"yes\"."
:tag "Org Store Link"
:group 'org-link)
-(defcustom org-link-url-hexify t
- "When non-nil, hexify URL when creating a link."
- :type 'boolean
- :version "24.3"
- :group 'org-link-store
- :safe #'booleanp)
-
(defcustom org-link-context-for-files t
"Non-nil means file links from `org-store-link' contain context.
\\<org-mode-map>
@@ -451,12 +444,6 @@ links more efficient."
;;; Public variables
-(defconst org-link-escape-chars
- ;;%20 %5B %5D %25
- '(?\s ?\[ ?\] ?%)
- "List of characters that should be escaped in a link when stored to Org.
-This is the list that is used for internal purposes.")
-
(defconst org-target-regexp (let ((border "[^<>\n\r \t]"))
(format "<<\\(%s\\|%s[^<>\n\r]*%s\\)>>"
border border border))
@@ -597,7 +584,7 @@ either a link description or nil."
(concat (format "%-45s" (substring desc 0 (min (length desc) 40)))
"<" (car link) ">")))
-(defun org-link--unescape-compound (hex)
+(defun org-link--decode-compound (hex)
"Unhexify Unicode hex-chars HEX.
E.g. \"%C3%B6\" is the German o-Umlaut. Note: this function also
decodes single byte encodings like \"%E1\" (a-acute) if not
@@ -628,14 +615,15 @@ followed by another \"%[A-F0-9]{2}\" group."
(setq ret (concat ret (char-to-string sum)))
(setq sum 0))
((not bytes) ; single byte(s)
- (setq ret (org-link--unescape-single-byte-sequence hex))))))
+ (setq ret (org-link--decode-single-byte-sequence hex))))))
ret)))
-(defun org-link--unescape-single-byte-sequence (hex)
+(defun org-link--decode-single-byte-sequence (hex)
"Unhexify hex-encoded single byte character sequence HEX."
(mapconcat (lambda (byte)
(char-to-string (string-to-number byte 16)))
- (cdr (split-string hex "%")) ""))
+ (cdr (split-string hex "%"))
+ ""))
(defun org-link--fontify-links-to-this-file ()
"Fontify links to the current file in `org-stored-links'."
@@ -750,7 +738,18 @@ This should be called after the variable `org-link-parameters' has changed."
"\\([^][ \t\n()<>]+\\(?:([[:word:]0-9_]+)\\|\\([^[:punct:] \t\n]\\|/\\)\\)\\)")
;; "\\([^]\t\n\r<>() ]+[^]\t\n\r<>,.;() ]\\)")
org-link-bracket-re
- "\\[\\[\\([^][]+\\)\\]\\(\\[\\([^][]+\\)\\]\\)?\\]"
+ (rx (seq "[["
+ ;; URI part: match group 1.
+ (group
+ (*? anything)
+ ;; Allow an even number of backslashes right
+ ;; before the closing bracket.
+ (not (any "\\"))
+ (zero-or-more "\\\\"))
+ "]"
+ ;; Description (optional): match group 2.
+ (opt "[" (group (+? anything)) "]")
+ "]"))
org-link-any-re
(concat "\\(" org-link-bracket-re "\\)\\|\\("
org-link-angle-re "\\)\\|\\("
@@ -841,55 +840,71 @@ and dates."
(setq org-store-link-plist
(plist-put org-store-link-plist key value)))))
-(defun org-link-escape (text &optional table merge)
- "Return percent escaped representation of TEXT.
-TEXT is a string with the text to escape.
-Optional argument TABLE is a list with characters that should be
-escaped. When nil, `org-link-escape-chars' is used.
-If optional argument MERGE is set, merge TABLE into
-`org-link-escape-chars'."
- (let ((characters-to-encode
- (cond ((null table) org-link-escape-chars)
- (merge (append org-link-escape-chars table))
- (t table))))
- (mapconcat
- (lambda (c)
- (if (or (memq c characters-to-encode)
- (and org-link-url-hexify (or (< c 32) (> c 126))))
- (mapconcat (lambda (e) (format "%%%.2X" e))
- (or (encode-coding-char c 'utf-8)
- (error "Unable to percent escape character: %c" c))
- "")
- (char-to-string c)))
- text "")))
-
-(defun org-link-unescape (str)
- "Unhex hexified Unicode parts in string STR.
-E.g. \"%C3%B6\" becomes the german o-Umlaut. This is the
-reciprocal of `org-link-escape', which see."
- (if (org-string-nw-p str)
- (replace-regexp-in-string
- "\\(%[0-9A-Za-z]\\{2\\}\\)+" #'org-link--unescape-compound str t t)
- str))
+(defun org-link-encode (text table)
+ "Return percent escaped representation of string TEXT.
+TEXT is a string with the text to escape. TABLE is a list of
+characters that should be escaped."
+ (mapconcat
+ (lambda (c)
+ (if (memq c table)
+ (mapconcat (lambda (e) (format "%%%.2X" e))
+ (or (encode-coding-char c 'utf-8)
+ (error "Unable to percent escape character: %c" c))
+ "")
+ (char-to-string c)))
+ text ""))
+
+(defun org-link-decode (s)
+ "Decode percent-encoded parts in string S.
+E.g. \"%C3%B6\" becomes the german o-Umlaut."
+ (replace-regexp-in-string "\\(%[0-9A-Za-z]\\{2\\}\\)+"
+ #'org-link--decode-compound s t t))
+
+(defun org-link-escape (link)
+ "Backslash-escape sensitive characters in string LINK."
+ ;; Escape closing square brackets followed by another square bracket
+ ;; or at the end of the link. Also escape final backslashes so that
+ ;; we do not escape inadvertently URI's closing bracket.
+ (with-temp-buffer
+ (insert link)
+ (insert (make-string (- (skip-chars-backward "\\\\"))
+ ?\\))
+ (while (search-backward "\]" nil t)
+ (when (looking-at-p "\\]\\(?:[][]\\|\\'\\)")
+ (insert (make-string (1+ (- (skip-chars-backward "\\\\")))
+ ?\\))))
+ (buffer-string)))
+
+(defun org-link-unescape (link)
+ "Remove escaping backslash characters from string LINK."
+ (with-temp-buffer
+ (save-excursion (insert link))
+ (while (re-search-forward "\\(\\\\+\\)\\]\\(?:[][]\\|\\'\\)" nil t)
+ (replace-match (make-string (/ (- (match-end 1) (match-beginning 1)) 2)
+ ?\\)
+ nil t nil 1))
+ (goto-char (point-max))
+ (delete-char (/ (- (skip-chars-backward "\\\\")) 2))
+ (buffer-string)))
(defun org-link-make-string (link &optional description)
- "Make a bracket link, consisting of LINK and DESCRIPTION."
+ "Make a bracket link, consisting of LINK and DESCRIPTION.
+LINK is escaped with backslashes for inclusion in buffer."
(unless (org-string-nw-p link) (error "Empty link"))
- (let ((uri (cond ((string-match org-link-types-re link)
- (concat (match-string 1 link)
- (org-link-escape (substring link (match-end 1)))))
- ((or (file-name-absolute-p link)
- (string-match-p "\\`\\.\\.?/" link))
- (org-link-escape link))
- ;; For readability, do not encode space characters
- ;; in fuzzy links.
- (t (org-link-escape link (remq ?\s org-link-escape-chars)))))
- (description
- (and (org-string-nw-p description)
- ;; Remove brackets from description, as they are fatal.
- (replace-regexp-in-string
- "[][]" (lambda (m) (if (equal "[" m) "{" "}"))
- (org-trim description)))))
+ (let* ((uri (org-link-escape link))
+ (zero-width-space (string ?\x200B))
+ (description
+ (and (org-string-nw-p description)
+ ;; Description cannot contain two consecutive square
+ ;; brackets, or end with a square bracket. To prevent
+ ;; this, insert a zero width space character between
+ ;; the brackets, or at the end of the description.
+ (replace-regexp-in-string
+ "\\(]\\)\\(]\\)"
+ (concat "\\1" zero-width-space "\\2")
+ (replace-regexp-in-string "]\\'"
+ (concat "\\&" zero-width-space)
+ (org-trim description))))))
(format "[[%s]%s]"
uri
(if description (format "[%s]" description) ""))))
@@ -1207,7 +1222,7 @@ If there is no description, use the link target."
(save-match-data
(replace-regexp-in-string
org-link-bracket-re
- (lambda (m) (or (match-string 3 m) (match-string 1 m)))
+ (lambda (m) (or (match-string 2 m) (match-string 1 m)))
s nil t)))
(defun org-link-add-angle-brackets (s)
@@ -1662,7 +1677,7 @@ don't allow to edit the default description."
((org-in-regexp org-link-bracket-re 1)
;; We do have a link at point, and we are going to edit it.
(setq remove (list (match-beginning 0) (match-end 0)))
- (setq desc (when (match-end 3) (match-string-no-properties 3)))
+ (setq desc (when (match-end 2) (match-string-no-properties 2)))
(setq link (read-string "Link: "
(org-link-unescape
(match-string-no-properties 1)))))
diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el
index d362ae2..90b8802 100644
--- a/lisp/org-agenda.el
+++ b/lisp/org-agenda.el
@@ -5201,7 +5201,7 @@ function from a program - use `org-agenda-get-day-entries' instead."
(when results
(setq results
(mapcar (lambda (i) (replace-regexp-in-string
- org-link-bracket-re "\\3" i)) results))
+ org-link-bracket-re "\\2" i)) results))
(concat (org-agenda-finalize-entries results) "\n"))))
;;; Agenda entry finders
@@ -6571,9 +6571,7 @@ Any match of REMOVE-RE will be removed from TXT."
level (or level ""))
(if (string-match org-link-bracket-re category)
(progn
- (setq l (if (match-end 3)
- (- (match-end 3) (match-beginning 3))
- (- (match-end 1) (match-beginning 1))))
+ (setq l (string-width (or (match-string 2) (match-string 1))))
(when (< l (or org-prefix-category-length 0))
(setq category (copy-sequence category))
(org-add-props category nil
@@ -10238,7 +10236,7 @@ to override `appt-message-warning-time'."
(lambda (x)
(let* ((evt (org-trim
(replace-regexp-in-string
- org-link-bracket-re "\\3"
+ org-link-bracket-re "\\2"
(or (get-text-property 1 'txt x) ""))))
(cat (get-text-property (1- (length x)) 'org-category x))
(tod (get-text-property 1 'time-of-day x))
diff --git a/lisp/org-clock.el b/lisp/org-clock.el
index 71f5122..317256a 100644
--- a/lisp/org-clock.el
+++ b/lisp/org-clock.el
@@ -2621,10 +2621,10 @@ from the dynamic block definition."
(if (and (string-match
(format "\\`%s\\'" org-link-bracket-re)
headline)
- (match-end 3))
+ (match-end 2))
(format "[[%s][%s]]"
(match-string 1 headline)
- (org-shorten-string (match-string 3 headline)
+ (org-shorten-string (match-string 2 headline)
narrow))
(org-shorten-string headline narrow))))
(cl-flet ((format-field (f) (format (cond ((not emph) "%s |")
diff --git a/lisp/org-compat.el b/lisp/org-compat.el
index 65980ad..44bdbb3 100644
--- a/lisp/org-compat.el
+++ b/lisp/org-compat.el
@@ -462,9 +462,6 @@ use of this function is for the stuck project list."
(define-obsolete-variable-alias 'org-descriptive-links
'org-link-descriptive "Org 9.3")
-(define-obsolete-variable-alias 'org-url-hexify-p
- 'org-link-url-hexify "Org 9.3")
-
(define-obsolete-variable-alias 'org-context-in-file-links
'org-link-context-for-files "Org 9.3")
diff --git a/lisp/org-element.el b/lisp/org-element.el
index 8d4e49d..d9ee8fd 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -3141,8 +3141,8 @@ Assume point is at the beginning of the link."
;; Type 2: Standard link, i.e. [[https://orgmode.org][homepage]]
((looking-at org-link-bracket-re)
(setq format 'bracket)
- (setq contents-begin (match-beginning 3))
- (setq contents-end (match-end 3))
+ (setq contents-begin (match-beginning 2))
+ (setq contents-end (match-end 2))
(setq link-end (match-end 0))
;; RAW-LINK is the original link. Decode any encoding.
;; Expand any abbreviation in it.
diff --git a/lisp/org-mobile.el b/lisp/org-mobile.el
index 1e6d357..854f7ce 100644
--- a/lisp/org-mobile.el
+++ b/lisp/org-mobile.el
@@ -31,14 +31,10 @@
;; iPhone and Android - any external viewer/flagging/editing
;; application that uses the same conventions could be used.
+(require 'cl-lib)
(require 'org)
(require 'org-agenda)
-(require 'cl-lib)
-
-(declare-function org-link-escape "ol" (text &optional table merge))
-(declare-function org-link-unescape "ol" (str))
-
-(defvar org-agenda-keep-restricted-file-list)
+(require 'ol)
;;; Code:
@@ -673,8 +669,7 @@ The table of checksums is written to the file mobile-checksums."
(org-mobile-escape-olp (nth 4 (org-heading-components))))))
(defun org-mobile-escape-olp (s)
- (let ((table '(?: ?/)))
- (org-link-escape s table)))
+ (org-link-encode s '(?: ?/)))
(defun org-mobile-create-sumo-agenda ()
"Create a file that contains all custom agenda views."
@@ -968,7 +963,7 @@ is currently a noop.")
(if (not (string-match "\\`olp:\\(.*?\\)$" link))
nil
(let ((file (match-string 1 link)))
- (setq file (org-link-unescape file))
+ (setq file (org-link-decode file))
(setq file (expand-file-name file org-directory))
(save-excursion
(find-file file)
@@ -978,9 +973,9 @@ is currently a noop.")
(point-marker))))
(let ((file (match-string 1 link))
(path (match-string 2 link)))
- (setq file (org-link-unescape file))
+ (setq file (org-link-decode file))
(setq file (expand-file-name file org-directory))
- (setq path (mapcar 'org-link-unescape
+ (setq path (mapcar #'org-link-decode
(org-split-string path "/")))
(org-find-olp (cons file path))))))
diff --git a/lisp/org-protocol.el b/lisp/org-protocol.el
index ea2873e..3f48be1 100644
--- a/lisp/org-protocol.el
+++ b/lisp/org-protocol.el
@@ -301,7 +301,7 @@ results of that splitting are returned as a list."
(split-parts (split-string data sep)))
(cond ((not unhexify) split-parts)
((fboundp unhexify) (mapcar unhexify split-parts))
- (t (mapcar #'org-link-unescape split-parts)))))
+ (t (mapcar #'org-link-decode split-parts)))))
(defun org-protocol-flatten-greedy (param-list &optional strip-path replacement)
"Transform PARAM-LIST into a flat list for greedy handlers.
@@ -382,7 +382,7 @@ If INFO is already a property list, return it unchanged."
(while data
(setq result
(append result
- (list (pop data) (org-link-unescape (pop data))))))
+ (list (pop data) (org-link-decode (pop data))))))
result)
(let ((data (org-protocol-split-data info t org-protocol-data-separator)))
(if default-order
diff --git a/lisp/org.el b/lisp/org.el
index 28c0ce4..0f0cece 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -9389,7 +9389,7 @@ prefix argument (`C-u C-u C-u C-c C-w')."
(setq heading-text
(replace-regexp-in-string
org-link-bracket-re
- "\\3"
+ "\\2"
(or (nth 4 (org-heading-components))
""))))
(org-refile-get-location
diff --git a/testing/README b/testing/README
index add3c4f..2469d14 100644
--- a/testing/README
+++ b/testing/README
@@ -120,15 +120,15 @@ load and run the test suite with the following commands.
To run one test: Use this as a demo example of a failing test
#+BEGIN_SRC emacs-lisp
- (ert-deftest test-org/org-link-escape-ascii-character-demo-of-fail ()
+ (ert-deftest test-org/org-link-encode-ascii-character-demo-of-fail ()
(should (string= "%5B" ; Expecting %5B is correct.
- (org-link-escape "[")))
+ (org-link-encode "[")))
(should (string= "%5C" ; Expecting %5C is wrong, %5D correct.
- (org-link-escape "]"))))
+ (org-link-encode "]"))))
#+END_SRC
or evaluate the ~ert-deftest form~ of the test you want to run.
Then ~M-x ert RET
- test-org/org-link-escape-ascii-character-demo-of-fail RET~. When
+ test-org/org-link-encode-ascii-character-demo-of-fail RET~. When
not visible yet switch to the ERT results buffer named ~*ert*~.
When a test failed the ERT results buffer shows the details of the
first ~should~ that failed. See ~(info "(ert)Running Tests
diff --git a/testing/lisp/test-ol.el b/testing/lisp/test-ol.el
index e6c09e6..995da85 100644
--- a/testing/lisp/test-ol.el
+++ b/testing/lisp/test-ol.el
@@ -20,80 +20,106 @@
;;; Code:
-;;; (Un)Escape links
+;;; Decode and Encode Links
-(ert-deftest test-ol/escape-ascii-character ()
- "Escape an ascii character."
- (should
- (string=
- "%5B"
- (org-link-escape "["))))
-
-(ert-deftest test-ol/escape-ascii-ctrl-character ()
- "Escape an ascii control character."
- (should
- (string=
- "%09"
- (org-link-escape "\t"))))
-
-(ert-deftest test-ol/escape-multibyte-character ()
- "Escape an unicode multibyte character."
- (should
- (string=
- "%E2%82%AC"
- (org-link-escape "€"))))
-
-(ert-deftest test-ol/escape-custom-table ()
- "Escape string with custom character table."
- (should
- (string=
- "Foo%3A%42ar%0A"
- (org-link-escape "Foo:Bar\n" '(?\: ?\B)))))
+(ert-deftest test-ol/encode ()
+ "Test `org-link-encode' specifications."
+ ;; Regural test.
+ (should (string= "Foo%3A%42ar" (org-link-encode "Foo:Bar" '(?\: ?\B))))
+ ;; Encode an ASCII character.
+ (should (string= "%5B" (org-link-encode "[" '(?\[))))
+ ;; Encode an ASCII control character.
+ (should (string= "%09" (org-link-encode "\t" '(9))))
+ ;; Encode a Unicode multibyte character.
+ (should (string= "%E2%82%AC" (org-link-encode "€" '(?\€)))))
-(ert-deftest test-ol/escape-custom-table-merge ()
- "Escape string with custom table merged with default table."
- (should
- (string=
- "%5BF%6F%6F%3A%42ar%0A%5D"
- (org-link-escape "[Foo:Bar\n]" '(?\: ?\B ?\o) t))))
+(ert-deftest test-ol/decode ()
+ "Test `org-link-decode' specifications."
+ ;; Decode an ASCII character.
+ (should (string= "[" (org-link-decode "%5B")))
+ ;; Decode an ASCII control character.
+ (should (string= "\n" (org-link-decode "%0A")))
+ ;; Decode a Unicode multibyte character.
+ (should (string= "€" (org-link-decode "%E2%82%AC"))))
-(ert-deftest test-ol/unescape-ascii-character ()
- "Unescape an ascii character."
+(ert-deftest test-ol/encode-url-with-escaped-char ()
+ "Encode and decode a URL that includes an encoded char."
(should
- (string=
- "["
- (org-link-unescape "%5B"))))
+ (string= "http://some.host.com/form?&id=blah%2Bblah25"
+ (org-link-decode
+ (org-link-encode "http://some.host.com/form?&id=blah%2Bblah25"
+ '(?\s ?\[ ?\] ?%))))))
-(ert-deftest test-ol/unescape-ascii-ctrl-character ()
- "Unescpae an ascii control character."
- (should
- (string=
- "\n"
- (org-link-unescape "%0A"))))
+
+;;; Escape and Unescape Links
-(ert-deftest test-ol/unescape-multibyte-character ()
- "Unescape unicode multibyte character."
- (should
- (string=
- "€"
- (org-link-unescape "%E2%82%AC"))))
+(ert-deftest test-ol/escape ()
+ "Test `org-link-escape' specifications."
+ ;; No-op when there is no backslash or closing square bracket.
+ (should (string= "foo[" (org-link-escape "foo[")))
+ ;; Escape closing square bracket at the end of the link.
+ (should (string= "[foo\\]" (org-link-escape "[foo]")))
+ ;; Escape closing square brackets followed by another square
+ ;; bracket.
+ (should (string= "foo\\][bar" (org-link-escape "foo][bar")))
+ (should (string= "foo\\]]bar" (org-link-escape "foo]]bar")))
+ ;; However, escaping closing square bracket at the end of the link
+ ;; has precedence over the previous rule.
+ (should (string= "foo]\\]" (org-link-escape "foo]]")))
+ ;; Escape backslashes at the end of the link.
+ (should (string= "foo\\\\" (org-link-escape "foo\\")))
+ ;; Escape backslashes that could be confused with escaping
+ ;; characters.
+ (should (string= "foo\\\\\\]" (org-link-escape "foo\\]")))
+ (should (string= "foo\\\\\\][" (org-link-escape "foo\\][")))
+ (should (string= "foo\\\\\\]]bar" (org-link-escape "foo\\]]bar")))
+ ;; Do not escape backslash characters when unnecessary.
+ (should (string= "foo\\bar" (org-link-escape "foo\\bar")))
+ (should (string= "foo\\]bar" (org-link-escape "foo\\]bar")))
+ ;; Pathological cases: consecutive closing square brackets.
+ (should (string= "[[[foo\\]]\\]" (org-link-escape "[[[foo]]]")))
+ (should (string= "[[[foo]\\]] bar" (org-link-escape "[[[foo]]] bar"))))
-(ert-deftest test-ol/unescape-ascii-extended-char ()
- "Unescape old style percent escaped character."
- (should
- (string=
- "àâçèéêîôùû"
- (decode-coding-string
- (org-link-unescape "%E0%E2%E7%E8%E9%EA%EE%F4%F9%FB") 'latin-1))))
+(ert-deftest test-ol/unescape ()
+ "Test `org-link-unescape' specifications."
+ ;; No-op if there is no backslash.
+ (should (string= "foo[" (org-link-unescape "foo[")))
+ ;; No-op if backslashes are not escaping backslashes.
+ (should (string= "foo\\bar" (org-link-unescape "foo\\bar")))
+ (should (string= "foo\\]bar" (org-link-unescape "foo\\]bar")))
+ ;;
+ (should (string= "foo\\]" (org-link-unescape "foo\\\\\\]")))
+ (should (string= "foo\\][" (org-link-unescape "foo\\\\\\][")))
+ (should (string= "foo\\]]bar" (org-link-unescape "foo\\\\\\]]bar")))
+ ;; Unescape backslashes at the end of the link.
+ (should (string= "foo\\" (org-link-unescape "foo\\\\")))
+ ;; Unescape closing square bracket at the end of the link.
+ (should (string= "[foo]" (org-link-unescape "[foo\\]")))
+ ;; Pathological cases: consecutive closing square brackets.
+ (should (string= "[[[foo]]]" (org-link-unescape "[[[foo\\]]\\]")))
+ (should (string= "[[[foo]]] bar" (org-link-unescape "[[[foo]\\]] bar"))))
-(ert-deftest test-ol/escape-url-with-escaped-char ()
- "Escape and unescape a URL that includes an escaped char.
-http://article.gmane.org/gmane.emacs.orgmode/21459/"
- (should
- (string=
- "http://some.host.com/form?&id=blah%2Bblah25"
- (org-link-unescape
- (org-link-escape "http://some.host.com/form?&id=blah%2Bblah25")))))
+(ert-deftest test-ol/make-string ()
+ "Test `org-link-make-string' specifications."
+ ;; Throw an error on empty URI.
+ (should-error (org-link-make-string ""))
+ ;; Empty description returns a [[URI]] construct.
+ (should (string= "[[uri]]"(org-link-make-string "uri")))
+ ;; Non-empty description returns a [[URI][DESCRIPTION]] construct.
+ (should
+ (string= "[[uri][description]]"
+ (org-link-make-string "uri" "description")))
+ ;; Escape "]]" strings in the description with zero-width spaces.
+ (should
+ (let ((zws (string ?\x200B)))
+ (string= (format "[[uri][foo]%s]bar]]" zws)
+ (org-link-make-string "uri" "foo]]bar"))))
+ ;; Prevent description from ending with a closing square bracket
+ ;; with a zero-width space.
+ (should
+ (let ((zws (string ?\x200B)))
+ (string= (format "[[uri][foo]%s]]" zws)
+ (org-link-make-string "uri" "foo]")))))
;;; Store links
diff --git a/testing/lisp/test-org-clock.el b/testing/lisp/test-org-clock.el
index fa336f6..1b32305 100644
--- a/testing/lisp/test-org-clock.el
+++ b/testing/lisp/test-org-clock.el
@@ -590,18 +590,18 @@ CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00"
;; Otherwise, link to the headline in the current file.
(should
(equal
- "| Headline | Time |
-|--------------+---------|
-| *Total time* | *26:00* |
-|--------------+---------|
-| [[file:filename::Foo][Foo]] | 26:00 |"
+ "| Headline | Time |
+|-------------------------------+---------|
+| *Total time* | *26:00* |
+|-------------------------------+---------|
+| [[file:filename::Foo][Foo]] | 26:00 |"
(org-test-with-temp-text-in-file
"* Foo
CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00"
(let ((file (buffer-file-name)))
(replace-regexp-in-string
(regexp-quote file) "filename"
- (test-org-clock-clocktable-contents ":link t"))))))
+ (test-org-clock-clocktable-contents ":link t :lang en"))))))
;; Ignore TODO keyword, priority cookie, COMMENT and tags in
;; headline.
(should
@@ -675,26 +675,26 @@ CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00"
;; links if there is no description.
(should
(equal
- "| Headline | Time |
-|--------------+---------|
-| *Total time* | *26:00* |
-|--------------+---------|
-| [[Foo %5B%5Bhttps://orgmode.org%5D%5BOrg mode%5D%5D][Foo Org mode]] | 26:00 |"
+ "| Headline | Time |
+|-----------------------------------------+---------|
+| *Total time* | *26:00* |
+|-----------------------------------------+---------|
+| [[Foo [[https://orgmode.org\\][Org mode]\\]][Foo Org mode]] | 26:00 |"
(org-test-with-temp-text
"* Foo [[https://orgmode.org][Org mode]]
CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00"
- (test-org-clock-clocktable-contents ":link t"))))
+ (test-org-clock-clocktable-contents ":link t :lang en"))))
(should
(equal
- "| Headline | Time |
-|-------------------------+---------|
-| *Total time* | *26:00* |
-|-------------------------+---------|
-| [[Foo %5B%5Bhttps://orgmode.org%5D%5D][Foo https://orgmode.org]] | 26:00 |"
+ "| Headline | Time |
+|------------------------------+---------|
+| *Total time* | *26:00* |
+|------------------------------+---------|
+| [[Foo [[https://orgmode.org]\\]][Foo https://orgmode.org]] | 26:00 |"
(org-test-with-temp-text
"* Foo [[https://orgmode.org]]
CLOCK: [2016-12-27 Wed 13:09]--[2016-12-28 Wed 15:09] => 26:00"
- (test-org-clock-clocktable-contents ":link t")))))
+ (test-org-clock-clocktable-contents ":link t :lang en")))))
(ert-deftest test-org-clock/clocktable/compact ()
"Test \":compact\" parameter in Clock table."
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index a70c18f..abc0046 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -2331,7 +2331,7 @@ SCHEDULED: <2014-03-04 tue.>"
;; Handle escape characters.
(should
(org-test-with-temp-text
- "* H1\n:PROPERTIES:\n:CUSTOM_ID: [%]\n:END:\n* H2\n[[#%5B%25%5D<point>]]"
+ "* H1\n:PROPERTIES:\n:CUSTOM_ID: [%]\n:END:\n* H2\n[[#[%\\]<point>]]"
(org-open-at-point)
(looking-at-p "\\* H1")))
;; Throw an error on false positives.
@@ -2425,13 +2425,9 @@ Foo Bar
(org-test-with-temp-text "[[*Test]]\n* TODO COMMENT Test"
(org-open-at-point)
(looking-at "\\* TODO COMMENT Test")))
- ;; Correctly un-hexify fuzzy links.
+ ;; Correctly un-escape fuzzy links.
(should
- (org-test-with-temp-text "* With space\n[[*With%20space][With space<point>]]"
- (org-open-at-point)
- (bobp)))
- (should
- (org-test-with-temp-text "* [1]\n[[*%5B1%5D<point>]]"
+ (org-test-with-temp-text "* [foo]\n[[*[foo\\]][With escaped characters]]"
(org-open-at-point)
(bobp)))
;; Match search strings containing newline characters, including
diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el
index 3bd2622..43637b9 100644
--- a/testing/lisp/test-ox.el
+++ b/testing/lisp/test-ox.el
@@ -3510,9 +3510,9 @@ Another text. (ref:text)
(org-element-type
(org-export-resolve-fuzzy-link
(org-element-map tree 'link 'identity info t) info)))))
- ;; Handle url-encoded fuzzy links.
+ ;; Handle escaped fuzzy links.
(should
- (org-test-with-parsed-data "* A B\n[[A%20B]]"
+ (org-test-with-parsed-data "* [foo]\n[[[foo\\]]]"
(org-export-resolve-fuzzy-link
(org-element-map tree 'link #'identity info t) info))))