How to distribute strings in Emacs or Vim

In Emacs or Vim, what a smooth way to join strings, as in this example:

  Transform from:
     (alpha, beta, gamma) blah (123, 456, 789)
 To:
     (alpha = 123, beta = 456, gamma = 789)

For scaling it is necessary:

  • many lines of these
  • many items in parentheses

Recently, I often needed such a conversion.

I use Evil in Emacs, so Vim's answer will probably also help.

UPDATE:

The solutions were not as general as I had hoped. For example, I would like the solution to also work when I have a list of strings and want to distribute them into a large XML document. eg:

<item foo="" bar="barval1"/> <item foo="" bar="barval2"/> <item foo="" bar="barval3"/> <item foo="" bar="barval4"/> fooval1 fooval2 fooval3 fooval4 

I formulated a solution and added it as an answer.

+6
source share
6 answers

My approach is to create one command to establish a match list, and then use replace-regexp as the second command to distribute the hit list using replace-regexp existing \, facility.

Compute Elisp, for example, in a .emacs file:

 (defvar match-list nil "A list of matches, as set through the set-match-list and consumed by the cycle-match-list function. ") (defvar match-list-iter nil "Iterator through the global match-list variable. ") (defun reset-match-list-iter () "Set match-list-iter to the beginning of match-list and return it. " (interactive) (setq match-list-iter match-list)) (defun make-match-list (match-regexp use-regexp beg end) "Set the match-list variable as described in the documentation for set-match-list. " ;; Starts at the beginning of region, searches forward and builds match-list. ;; For efficiency, matches are appended to the front of match-list and then reversed ;; at the end. ;; ;; Note that the behavior of re-search-backward is such that the same match-list ;; is not created by starting at the end of the region and searching backward. (let ((match-list nil)) (save-excursion (goto-char beg) (while (let ((old-pos (point)) (new-pos (re-search-forward match-regexp end t))) (when (equal old-pos new-pos) (error "re-search-forward makes no progress. old-pos=%s new-pos=%s end=%s match-regexp=%s" old-pos new-pos end match-regexp)) new-pos) (setq match-list (cons (replace-regexp-in-string match-regexp use-regexp (match-string 0) t) match-list))) (setq match-list (nreverse match-list))))) (defun set-match-list (match-regexp use-regexp beg end) "Set the match-list global variable to a list of regexp matches. MATCH-REGEXP is used to find matches in the region from BEG to END, and USE-REGEXP is the regexp to place in the match-list variable. For example, if the region contains the text: {alpha,beta,gamma} and MATCH-REGEXP is: \\([az]+\\), and USE-REGEXP is: \\1 then match-list will become the list of strings: (\"alpha\" \"beta\")" (interactive "sMatch regexp: \nsPlace in match-list: \nr") (setq match-list (make-match-list match-regexp use-regexp beg end)) (reset-match-list-iter)) (defun cycle-match-list (&optional after-end-string) "Return the next element of match-list. If AFTER-END-STRING is nil, cycle back to the beginning of match-list. Else return AFTER-END-STRING once the end of match-list is reached." (let ((ret-elm (car match-list-iter))) (unless ret-elm (if after-end-string (setq ret-elm after-end-string) (reset-match-list-iter) (setq ret-elm (car match-list-iter)))) (setq match-list-iter (cdr match-list-iter)) ret-elm)) (defadvice replace-regexp (before my-advice-replace-regexp activate) "Advise replace-regexp to support match-list functionality. " (reset-match-list-iter)) 

Then, to solve the original problem:

 Mx set-match-list Match regexp: \([0-9]+\)[,)] Place in match-list: \1 Mx replace-regexp Replace regexp: \([az]+\)\([,)]\) Replace regexp with: \1=\,(cycle-match-list)\2 

And to solve the XML example:

 [Select fooval strings.] Mx set-match-list Match regexp: .+ Place in match-list: \& [Select XML tags.] Mx replace-regexp Replace regexp: foo="" Replace regexp with: foo="\,(cycle-match-list)" 
0
source
 %s/(\(\S\{-}\), \(\S\{-}\), \(\S\{-}\)).\{-}(\(\S\{-}\), \(\S\{-}\), \(\S\{-}\))/(\1=\4, \2=\5, \3=\6) 

%s : global search and replace

\(\S{-}\), ,: do not greedily search for non-white characters until the next comma enclosed in "(" for callback

\1=\4 : returns the first match, the "=" sign, then the fourth match

+2
source

for such a text conversion, I would go with awk:

This one line can help:

 awk -F'\\(|\\)' '{split($2,t,",");split($4,v,",");printf "( "; for(x in t)s=s""sprintf("%s=%s, ", t[x],v[x]);sub(", $","",s);printf s")\n";s=""}' file 

small test:

 kent$ cat test (alpha, beta, gamma) blah (123, 456, 789) (a, b, c) foo (1, 2, 3) (x, y, z, m, n) bar (100, 200, 300, 400, 500) kent$ awk -F'\\(|\\)' '{split($2,t,",");split($4,v,",");printf "( "; for(x in t)s=s""sprintf("%s=%s, ", t[x],v[x]);sub(", $","",s);printf s")\n";s=""}' test ( alpha=123, beta= 456, gamma= 789) ( a=1, b= 2, c= 3) ( m= 400, n= 500, x=100, y= 200, z= 300) 
+2
source

Here is the Vimscript solution. This is nowhere more elegant than ashes, but it works with lists of any length.

 function! ListMerge() " Get line, remove text between lists, split lists at parentheses: let curline = getline('.') let curline = substitute(curline,')\zs.*\ze(','','g') let curline = substitute(curline,'(','','g') let lists = map(split(curline,')'),'split(v:val,",")') " Return if we don't have two lists of equal length: if len(lists) != 2 || len(lists[0]) != len(lists[1]) return endif " Loop over the lists, remove whitespace, build the replacement string: let i=0 let string = '(' while i<len(lists[0]) let string .= substitute(lists[0][i],'^ *','','') let string .= '=' let string .= substitute(lists[1][i],'^ *','','') let string .= ', ' let i+=1 endwhile " Add the concluding bracket: let string = substitute(string,', $',')','') " Replace the current line with the string: execute "normal! S" . string endfunction 

Then you can call this function on all lines as follows:

 :%call ListMerge() 
+1
source

Emacs Lisp version of Prince Goulash's answer

 (require 'cl) (defun split-and-trim (str separator) (let ((strs (split-string str separator))) (mapcar (lambda (s) (replace-regexp-in-string "^\\s-+" "" s)) (mapcar (lambda (s) (replace-regexp-in-string "\\s-$" "" s)) strs)))) (defun my/merge-list (beg end) (interactive "r") (goto-char beg) (let ((endmark (set-mark end)) (regexp "(\\([^)]+\\))[^(]+(\\([^)]+\\))")) (while (re-search-forward regexp end t) (let ((replace-start (match-beginning 0)) (replace-end (match-end 0)) (keys-str (match-string-no-properties 1)) (values-str (match-string-no-properties 2))) (let* ((keys (split-and-trim keys-str ",")) (values (split-and-trim values-str ","))) (while (> (length keys) (length values)) (setq values (append values '("")))) (let* ((pairs (mapcar* (lambda (kv) (format "%s=%s" kv)) keys values)) (transformed (format "(%s)" (mapconcat #'identity pairs ", ")))) (goto-char replace-start) (delete-region replace-start replace-end) (insert transformed))))) (goto-char (marker-position endmark)))) 

For example, you select an area as follows

 (alpha, beta, gamma) blah (123, 456, 789) (alpha, beta, gamma, delta) blah (123, 456, 789, aaa) 

After Mx my / merge-list

 (alpha=123, beta=456, gamma=789) (alpha=123, beta=456, gamma=789, delta=aaa) 
+1
source

This method I'm going to describe is a bit strange, but it includes the minimum amount of Elisp code that I could control. This is only applicable if the lists to be combined can be interpreted as Lisp lists after removing the commas in them. Numbers and sequences of alphabetic characters, as in your example, will be in order.

First make sure that the Common Lisp library is loaded: M-: (require 'cl) RET .

Now, starting with the cursor at the beginning of the first list:

MCk ; kill forward-sexp

Ce ; end-row entry

MCb ; back-sexp

MCk ; kill forward-sexp

Ca ; move starting-line

Ck ; sling

Now blah (or something else) is the first entry in the annihilation ring, the second list is the second entry, and the first list is the third.

Type ( , then M-: ( eval-expression ), take a deep breath and enter this:

 (loop with (ab) = (mapcar (lambda (x) (car (read-from-string (remove ?, x)))) (subseq kill-ring 1 3)) for x in a for y in b do (insert (format "%s=%s, " yx))) 

(I broke it for presentation purposes, but you can type it on one line.)

Then, finally, DEL DEL ) , and you're done! You can turn it into a macro if you want.

+1
source

All Articles