Why can't GIMP handle this script? - lisp

I have this script as an .scm in Gimp:
;MIT license.
(define (script-fu-export-layers img drw path outnameformat)
; credit to Vijay Mathew on Stack Overflow for the expand keywords function
(let ((expand-keywords (lambda(format tokens)
(let loop ((slist (string->list string))
(in-replace-mode #f)
(result ""))
(if (not (null? slist))
(let ((c (car slist)))
(cond (in-replace-mode
(let ((token (car (cdr (assoc c tokens)))))
(loop (cdr slist) #f (string-append result token))))
((char=? c #\~)
(loop (cdr slist) #t result))
(else
(loop (cdr slist) #f (
string-append result (make-string 1 c))))))
result)))))
(for-each (lambda (layer)
(let* (
(name (expand-keywords outnameformat '(
(#\i (car(gimp-image-get-name img)))
(#\l (car(gimp-drawable-get-name layer))))))
(outpath (string-append path "/" name)))
(gimp-file-save RUN-NONINTERACTIVE img layer outpath name)
)) (vector->list(cadr (gimp-image-get-layers img)))))
)
(script-fu-register
"script-fu-export-layers"
"L_ayers"
"Export all layers as individual files."
"Stuart P. Bentley <stuart#testtrack4.com>"
"Copyright 2011 Stuart P. Bentley"
"June 28, 2011"
"*"
SF-IMAGE "The Image" 0
SF-DRAWABLE "The Layer" 0
SF-DIRNAME "Output directory" ""
SF-STRING "Filename Format (~i = image name, ~l = layer name)"
"~i-~l.png"
)
(script-fu-menu-register "script-fu-export-layers" "<Image>/File/E_xport")
With the comment at the top of the file, the script doesn't load at all. When the comment is removed, GIMP throws an error when refreshing scripts that "Error while loading C:\Users\Stuart.gimp-2.6\scripts\export-layers.scm: Error: unmatched parentheses: 1". I don't know what that's about, since it parses fine when I run it on Codepad (it breaks at the script-fu-register point).

This behavior is consistent with what would happen if the line endings were missing (the comments commenting out all subsequent text). Check to make sure your editor isn't doing something ridiculous like saving your file with CR line endings on Windows.

Related

Functions inside a loop behaves differently

So I have a loop to just repeat the little text game I have made about dota, but when the function 'play' is called within a loop it doesn't return the result of my cond function, it just takes an input and then moves on to the next loop.
;;;;learn the invoker combo's
(defparameter *invoker-combo* '((cold-snap (3 0 0) 'QQQ);all of the possible invoker combo's
(ghost-walk (2 1 0) 'QQW)
(Ice-Wall (2 0 1) 'QQE)
(EMP (0 3 0) 'WWW)
(Tornado (1 2 0) 'QWW)
(Alacrity (0 2 1) 'WWE)
(Sun-Strike (0 0 3) 'EEE)
(Forge-Spirit (1 0 2) 'QEE)
(Chaos-Meteor (0 1 2) 'WEE)
(Deafening-Blast (1 1 1) 'QWE)))
(defun rand-combo (invoker-combo);returns a random combo
(nth (random (length invoker-combo))invoker-combo))
(defun count-letters (input);converts the keyboard strokes into numbers to be compared as it doesn't matter what order they are in, just that there is the correct quantity of them e.g QQE could also be written QEQ.
(append
(list (count #\Q input)
(count #\W input)
(count #\E input))))
(defun try-for-combo (rand-combo);takes i-p and compares it with the value for the random combo
(print(car rand-combo))
(let* ((i-p (string-upcase(read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo))'Good-job)
((equal i-p "END")(list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is (caddr(assoc (car rand-combo)*invoker-combo*)))))))
(defun play ()
(try-for-combo (rand-combo *invoker-combo*)))
(defun loop-play (x)
(loop for i from 0 to x
:do (play)))
If I call the function 'play' I get the following o/p:
FORGE-SPIRIT asdf
("ASDF" WAS WRONG CORRECT IS 'QEE)
or
ALACRITY wwe
GOOD-JOB
But if I call the function 'loop-play' I get the following o/p:
Break 3 [7]> (loop-play 2)
SUN-STRIKE eee
ALACRITY wwe
TORNADO qww
NIL
Can someone explain to me why this is happening?
EDIT: feel free to change the title, I didn't really know what to put.
The indentation and formatting of the code is poor. Please make it easier for you and for us to read the code.
(defun try-for-combo (rand-combo);takes i-p and compares it with the value for the random combo
(print(car rand-combo))
(let* ((i-p (string-upcase(read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo))'Good-job) ; wrong indent level
((equal i-p "END")(list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is (caddr(assoc (car rand-combo)*invoker-combo*)))))))
lacks spaces between s-expressions
wrong indentation levels
structure of the code unclear
does not use built-in documentation features
some lines are too long
Better:
(defun try-for-combo (rand-combo)
"takes i-p and compares it with the value for the random combo" ; built in doc
(print (car rand-combo))
(let* ((i-p (string-upcase (read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo)) ; indentation
'Good-job)
((equal i-p "END")
(list 'Thanks 'for 'playing))
(t
(list i-p 'was 'wrong 'correct 'is ; several lines
(caddr (assoc (car rand-combo)
*invoker-combo*)))))))
I would propose to use an editor which actually understands some Lisp formatting. like GNU Emacs / SLIME, Clozure CL's Hemlock, LispWorks' editor...
If you are unsure about formatting, you can also ask Lisp to do it. Clisp is not that good at formatting, but something like SBCL or CCL would do:
* (let ((*print-case* :downcase))
(pprint '(defun try-for-combo (rand-combo)
(print (car rand-combo))
(let* ((i-p (string-upcase (read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo))
'Good-job) ((equal i-p "END")
(list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is
(caddr (assoc (car rand-combo)
*invoker-combo*)))))))))
And you get nicely formatted code:
(defun try-for-combo (rand-combo)
(print (car rand-combo))
(let* ((i-p (string-upcase (read-line))) (try (count-letters i-p)))
(cond ((equal try (cadr rand-combo)) 'good-job)
((equal i-p "END") (list 'thanks 'for 'playing))
(t
(list i-p 'was 'wrong 'correct 'is
(caddr (assoc (car rand-combo) *invoker-combo*)))))))
Automatic indenting of Lisp code by the editor saves you a lot of work.
There are hints for manual indentation.
Your try-for-combo function doesn't actually output anything. Rather, it returns values.
In the REPL, if you evaluate a form, like (+ 1 2), it will always print the evaluation of that form at the end (in this case, 3). However, consider instead (+ 1 (print 2)). The print function actually outputs the argument to standard output, then returns the value itself. So this will show (on the repl)
2
3
The 2 is outputted first, because (print 2) itself prints 2. Then, the form (+ 1 (print 2)) is evaluates to the same things as (+ 1 2), or 3.
In your case, your try-for-combo function should look like:
(defun try-for-combo (rand-combo)
(print (car rand-combo))
(let* ((i-p (string-upcase(read-line)))
(try (count-letters i-p)))
(print
(cond
((equal try (cadr rand-combo)) 'Good-job)
((equal i-p "END") (list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is (caddr(assoc (car rand-combo) *invoker-combo*))))))
nil))
This will print the result of that cond form, and return 'nil'.
That's just the difference between the output your program does and the output the Lisp system does for each evaluation:
print prints something (a newline and then its argument) and returns a value. The value is printed by the REPL. Thus we see output twice:
[3]> (print "3")
"3"
"3"
Next we do several call to print in a progn. The value of the progn form is printed by the REPL. The first three strings are printed by the code and the last string is printed because of the Lisp REPL printing the value:
[4]> (progn (print "1") (print "2") (print "3"))
"1"
"2"
"3"
"3"

How do I find and insert the average of multiple lines in Emacs / Elisp?

I have a file that looks similar to:
AT 4
AT 5.6
AT 7.2
EG 6
EG 6
S 2
OP 3
OP 1.2
OP 40
and I want to compute the average (I've just made these averages up) for each of the titles and output something like:
AT 5.42
EG 6
S 2
OP 32.1
The file is in order, so all headings will be right under each other, but there are a varying amount of headings. eg. AT has three, but S only has one.
How would I sum together each of these lines, divide by the number of lines, and then replace all of the lines in emacs / elisp?
I decided to try to solve this question while still learning elisp myself. There is perhaps more efficient ways to solve this.
After defining the function, you'll want to set the region around the scores. (If the whole file, then M-<, C-SPC, M->) I figured this would be cleanest since your scores may be in the middle of other text. My function will compute the averages and then insert the answer at the end of the region.
(defun my/averages (beg end)
(interactive "r")
(let ((avgs (make-hash-table :test 'equal))
(answer "")
(curval nil)
(key nil)
(val nil))
; Process each line in region
(save-excursion
(goto-char beg)
(while (< (point) end)
; split line
(let ((split-line
(split-string
(buffer-substring-no-properties
(line-beginning-position) (line-end-position)))))
(setq
key (car split-line)
val (string-to-number (cadr split-line))
curval (gethash key avgs '(0 . 0)))
(puthash key (cons (+ (car curval) 1) (+ (cdr curval) val )) avgs))
; Advance to next line
(forward-line))
; Accumulate answer string
(maphash
(lambda (k v)
(setq answer
(concat answer "\n" k " "
(number-to-string (/ (cdr v) (car v))))))
avgs)
(end-of-line)
(insert answer))))
As a warning, I have zero error checking for lines that do not strictly meet your formatting.
You need libraries dash, s, f, and their functions -map, -sum, -group-by, s-split, f-read-text.
;; average
(defun avg (values)
(/ (-sum values) (length values)))
(-map (lambda (item)
(list (car item)
(avg (-map (lambda (x)
(string-to-number (cadr x)))
(cdr item)))))
(-group-by (lambda (item)
(car item))
(-map (lambda (line)
(s-split " " line t))
(s-split "[\n\r]"
(f-read-text "file.txt")
t))))
Presuming your file is called "file.txt", the code above returns (("AT" 5.6000000000000005) ("EG" 6) ("S" 2) ("OP" 14.733333333333334)).
After that you can convert that into text:
(s-join "\n"
(-map (lambda (item)
(s-join " "
(list (car item)
(number-to-string (cadr item)))))
This string you can write into file using f-write-text. Don't forget you can format ugly floating-point numbers like that:
(format "%.2f" 3.33333333) ; => "3.33"

How to eval Lisp code inside a reader macro?

I'm writing my own x86-64 assembler in Common Lisp and it produces correct binary code for a subset of x86-64. I use a custom reader macro to convert assembly code to a syntax tree, and it works as expected.
What I am attempting to accomplish is to allow using Lisp code inside assembly code, that way I could use Lisp as a macro language for my assembler. I use #a as the macro dispatch character and #e to signal end for the reader. Inside reader #l changes to Lisp mode and #a back to assembly mode, #e (to signal end for the reader macro) should work in both modes.
What I don't understand is how to output the results of the evaluated code back to the input stream (to be processed before the rest of the code), or otherwise how to get the Lisp code output be read again, so that the output of Lisp code (it would be assembly code) could be processed appropriately (the same way as the rest of the assembly code). How can I reach that goal?
A sidenote: this is my first reader macro, so there may be design flaws. I think my approach to read Lisp code into a string is not necessarily the best way, if there is some shorter and more idiomatic way to do it.
Here's a simplified version of my reader macro:
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun get-last-character-string (my-string)
"This function returns a string consisting of the last character of the input string."
(subseq my-string (1- (length my-string))))
(defun get-string-without-last-character (my-string)
"This function returns a string without the last character of the input string."
(subseq my-string 0 (1- (length my-string))))
(defun get-string-without-invalid-last-character (my-string invalid-last-characters)
"If the last character of the string is invalid, the string is returned without it, otherwise completely."
(loop for invalid-last-character in invalid-last-characters
do (if (equal (get-last-character-string my-string) invalid-last-character)
(setf my-string (get-string-without-last-character my-string))))
my-string)
(defun transform-code-to-string (stream sub-char numarg)
"This function converts assembly code into a string.
#l marks change to Lisp code. #a marks return to asm. #e marks end.
Partially based on: http://weitz.de/macros.lisp"
(declare (ignore sub-char numarg))
(let*
((invalid-last-characters (list "'" " " "(" ")"))
(current-mode "asm")
(is-there-code-on-this-line nil)
(current-phase "beginning-of-line")
(my-string "(list ")
(lisp-code-string ""))
;; loop through stream.
(loop for my-char = (coerce (list (read-char stream t nil t)) 'string)
do (cond
((equal current-mode "asm")
(cond
((equal current-phase "hash-sign-read")
;; is character e ?
;; if yes, we're done, fix closing parentheses and return.
(cond
((equal my-char "e")
(return-from transform-code-to-string
(concatenate 'string (get-string-without-invalid-last-character
(get-string-without-invalid-last-character
my-string invalid-last-characters)
invalid-last-characters) "))")))
;; is character l ?
;; if yes, change to Lisp mode.
((equal my-char "l")
;; could Lisp code could be read and evaluated here
;; without reading it into a string?
(progn
(setf current-mode "Lisp")
(setf is-there-code-on-this-line nil)
(setf lisp-code-string "")
(setf current-phase "beginning-of-line")))
;; otherwise, print error.
(t (error "in asm mode undefined control character after #"))))
;; is character # ?
;; if yes, mark hash sign read.
((equal my-char "#")
(setf current-phase "hash-sign-read"))
;; is character newline?
((equal my-char (coerce (list #\Newline) 'string))
(progn
(cond
;; is there _no_ code on this line?
;; if true, do not output anything.
((not is-there-code-on-this-line)
(setf current-phase "beginning-of-line"))
;; are we inside instruction or inside a parameter?
;; if true, output ")
((or (equal current-phase "inside-instruction")
(equal current-phase "inside-parameters"))
(progn
(setf current-phase "beginning-of-line")
(setf is-there-code-on-this-line nil)
(setf my-string (concatenate 'string my-string "\")"))))
;; otherwise output )
(t (progn
(setf current-phase "beginning-of-line")
(setf is-there-code-on-this-line nil)
(setf my-string (concatenate 'string my-string ")")))))))
;; are we inside a comment?
;; if yes, don't output anything.
((equal current-phase "inside-comment")
nil)
;; are we in the beginning of the line?
((equal current-phase "beginning-of-line")
(cond
;; is this a space in the beginning of the line?
;; if yes, do not output anything.
((equal my-char " ")
nil)
;; is this the first character of instruction and not ( or ) ?
;; if yes, mark there is code on this line, mark first character as printed, output " and current character.
((and
(not (equal my-char "("))
(not (equal my-char ")")))
(progn
(setf current-phase "inside-instruction")
(setf is-there-code-on-this-line t)
(setf my-string (concatenate 'string my-string "'(\"" my-char))))
(t nil)))
;; is character ; ?
;; if yes, don't output anything, begin comment.
((equal my-char ";")
(setf current-phase "inside-comment"))
;; is character space or comma?
((or (equal my-char " ")
(equal my-char ","))
(cond
;; is character space or comma, and last character was _not_ space, comma or opening parenthesis?
;; if yes, output " and space.
((and
(not (equal (get-last-character-string my-string) " "))
(not (equal (get-last-character-string my-string) ","))
(not (equal (get-last-character-string my-string) "(")))
(progn
(setf current-phase "in-space")
(setf my-string (concatenate 'string my-string "\" "))))
(t nil)))
;; is instruction printed and this is the 1st character of a parameter?
((and
(not (equal current-phase "inside-instruction"))
(or (equal (get-last-character-string my-string) " ")
(equal (get-last-character-string my-string) ",")))
(cond
;; mark we're inside parameters, output " and current character.
(t (progn
(setf current-phase "inside-parameters")
(setf my-string (concatenate 'string my-string "\"" my-char))))))
;; otherwise output the character.
(t (setf my-string (concatenate 'string my-string my-char)))))
((equal current-mode "Lisp")
;; in Lisp mode, read text until #e or #a is reached and eval it.
(cond
((equal current-phase "hash-sign-read")
(cond
;; is character e ?
;; if yes, we're done, fix closing parentheses and return.
((equal my-char "e")
(progn
(concatenate 'string "#a" (eval lisp-code-string) "#e") ; this should be something different.
(return-from transform-code-to-string
(concatenate 'string (get-string-without-invalid-last-character
(get-string-without-invalid-last-character
my-string invalid-last-characters)
invalid-last-characters) "))"))))
;; is character a ?
;; if yes, change to asm mode.
((equal my-char "a")
(progn
(setf current-mode "asm")
(setf is-there-code-on-this-line nil)
(setf current-phase "beginning-of-line")
(concatenate 'string "#a" (eval lisp-code-string) "#e") ; this should be something different.
;; otherwise, add # and the character to the Lisp code to be evaluated.
(t (progn
(setf current-phase "")
(setf my-string (concatenate 'string lisp-code-string "#" my-char))))))
;; is character # ?
;; if yes, mark hash sign read.
((equal my-char "#")
(setf current-phase "hash-sign-read"))
;; otherwise add the character to the Lisp code to be evaluated.
(t (setf my-string (concatenate 'string lisp-code-string my-char)))))
(t (error "invalid current mode"))))))
;;; #a is the input which starts the custom reader.
(set-dispatch-macro-character #\# #\a #'transform-code-to-string))
Here's some example assembly code without Lisp code inside, works:
(defparameter *example-code-x64*
#a
inc r10 ; increment register r10.
mov r11,r12 ; store value of r12 into r11.
#e)
And here's some assembly code with Lisp code inside, fails (see compiling error further below). In this one the Lisp code is after assembly code, but assembly and Lisp code should be allowed to be mixed freely using #a and #l as separators.
(defparameter *example-code-x64-with-lisp-fails*
#a
inc r10 ; increment register r10.
mov r11,r12 ; store value of r12 into r11.
#l
(loop for current-instruction in (list "inc" "dec")
do (loop for current-arg in (list "r13" "r14" "r15")
do (princ (concatenate 'string
current-instruction
" "
current-arg
(coerce (list #\Newline) 'string)))))
#e)
The Lisp part of the above code should be evaluated in the custom reader, so that it should produce identical results as the code below:
(defparameter *example-code-x64-with-lisp-fails*
#a
inc r10 ; increment register r10.
mov r11,r12 ; store value of r12 into r11.
inc r13
inc r14
inc r15
dec r13
dec r14
dec r15
#e)
But instead the compiling fails:
CL-USER> ; compiling file "/home/user/code/lisp/lisp-asm-reader-for-stackoverflow.lisp" (written 28 MAR 2014 10:11:29 PM):
;
; caught ERROR:
; READ error during COMPILE-FILE:
;
; The value -1 is not of type (MOD 4611686018427387901).
;
; (in form starting at line: 1, column: 0, file-position: 0)
;
; compilation unit aborted
; caught 1 fatal ERROR condition
; caught 1 ERROR condition
; compilation aborted after 0:00:00.004
1 compiler notes:
/home/user/code/lisp/lisp-asm-reader-for-stackoverflow.lisp:10487
read-error: READ error during COMPILE-FILE:
The value -1 is not of type (MOD 4611686018427387901).
(in form starting at line: 1, column: 0, file-position: 0)
CL-USER>
The idiomatic way to read lisp code from within a reader macro is to call cl:read. In your example, calling read after consuming #L will return the list whose car is loop, and that list can be passed to eval.
To collect the output created during the eval, you can bind *standard-output*. So an option is to use something akin to the following within your reader macro:
(let ((lisp-printed-string
(with-output-to-string (*standard-output*)
(eval (read stream t t t)))))
;; concatenate the lisp printed string onto your
;; hand parsed string here
)
An alternative is to have the user input a lisp form which returns a string {e.g. (concatenate "bar" "baz")}, and collect eval's return value instead of its printed output.

How to replace string in a file with lisp?

What's the lisp way of replacing a string in a file.
There is a file identified by *file-path*, a search string *search-term* and a replacement string *replace-term*.
How to make file with all instances of *search-term*s replaced with *replace-term*s, preferably in place of the old file?
One more take at the problem, but few warnings first:
To make this really robust and usable in the real-life situation you would need to wrap this into handler-case and handle various errors, like insufficient disc space, device not ready, insufficient permission for reading / writing, insufficient memory to allocate for the buffer and so on.
This does not do regular expression-like replacement, it's simple string replacement. Making a regular expression based replacement on large files may appear far less trivial than it looks like from the start, it would be worth writing a separate program, something like sed or awk or an entire language, like Perl or awk ;)
Unlike other solutions it will create a temporary file near the file being replaced and will save the data processed so far into this file. This may be worse in the sense that it will use more disc space, but this is safer because in case the program fails in the middle, the original file will remain intact, more than that, with some more effort you could later resume replacing from the temporary file if, for example, you were saving the offset into the original file in the temporary file too.
(defun file-replace-string (search-for replace-with file
&key (element-type 'base-char)
(temp-suffix ".tmp"))
(with-open-file (open-stream
file
:direction :input
:if-exists :supersede
:element-type element-type)
(with-open-file (temp-stream
(concatenate 'string file temp-suffix)
:direction :output
:element-type element-type)
(do ((buffer (make-string (length search-for)))
(buffer-fill-pointer 0)
(next-matching-char (aref search-for 0))
(in-char (read-char open-stream nil :eof)
(read-char open-stream nil :eof)))
((eql in-char :eof)
(when (/= 0 buffer-fill-pointer)
(dotimes (i buffer-fill-pointer)
(write-char (aref buffer i) temp-stream))))
(if (char= in-char next-matching-char)
(progn
(setf (aref buffer buffer-fill-pointer) in-char
buffer-fill-pointer (1+ buffer-fill-pointer))
(when (= buffer-fill-pointer (length search-for))
(dotimes (i (length replace-with))
(write-char (aref replace-with i) temp-stream))
(setf buffer-fill-pointer 0)))
(progn
(dotimes (i buffer-fill-pointer)
(write-char (aref buffer i) temp-stream))
(write-char in-char temp-stream)
(setf buffer-fill-pointer 0)))
(setf next-matching-char (aref search-for buffer-fill-pointer)))))
(delete-file file)
(rename-file (concatenate 'string file temp-suffix) file))
It can be accomplished in many ways, for example with regexes. The most self-contained way I see is something like the following:
(defun replace-in-file (search-term file-path replace-term)
(let ((contents (rutil:read-file file-path)))
(with-open-file (out file-path :direction :output :if-exists :supersede)
(do* ((start 0 (+ pos (length search-term)))
(pos (search search-term contents)
(search search-term contents :start2 start)))
((null pos) (write-string (subseq contents start) out))
(format out "~A~A" (subseq contents start pos) replace-term))))
(values))
See the implementation of rutil:read-file here: https://github.com/vseloved/rutils/blob/master/core/string.lisp#L33
Also note, that this function will replace search terms with any characters, including newlines.
in chicken scheme with the ireggex egg:
(use irregex) ; irregex, the regular expression library, is one of the
; libraries included with CHICKEN.
(define (process-line line re rplc)
(irregex-replace/all re line rplc))
(define (quickrep re rplc)
(let ((line (read-line)))
(if (not (eof-object? line))
(begin
(display (process-line line re rplc))
(newline)
(quickrep re rplc)))))
(define (main args)
(quickrep (irregex (car args)) (cadr args)))
Edit: in the above example buffering the input doesn't permit the regexp to span over
many lines.
To counter that here is an even simpler implementation which scans the whole file as one string:
(use ireggex)
(use utils)
(define (process-line line re rplc)
(irregex-replace/all re line rplc))
(define (quickrep re rplc file)
(let ((line (read-all file)))
(display (process-line line re rplc))))
(define (main args)
(quickrep (irregex (car args)) (cadr args) (caddr args)))

emacs/openwith how to pass arguments to program?

I am using the openwith package in emacs. I would like to open .fig files with xfig with some additional options, for example:
xfig -specialtext -latexfont -startlatexFont default file.fig
openwith is working for me with other file associations where I don't need to pass additional options. I tried the following in my .emacs file
(setq
openwith-associations
'(("\\.fig\\'" "xfig" (file))))
which works, but
(setq
openwith-associations
'(("\\.fig\\'" "xfig -specialtext -latexfont -startlatexFont default" (file))))
does not work (error: Wrong type argument: arrayp, nil), also
(setq
openwith-associations
'(("\\.fig\\'" "xfig" (" -specialtext -latexfont -startlatexFont default " file))))
does not work, although here I don't get any error. It says "Opened file.fig in external program" but nothing happens. In this case, I notice that there is an xfig process running with all these options.
Could someone let me know how to fix this?
Thanks for the help.
I have no clue how this works, so I just document how one can figure it by reading the code:
The important code in openwith.el is the call to start-process in:
(dolist (oa openwith-associations)
(let (match)
(save-match-data
(setq match (string-match (car oa) (car args))))
(when match
(let ((params (mapcar (lambda (x)
(if (eq x 'file)
(car args)
(format "%s" x))) (nth 2 oa))))
(apply #'start-process "openwith-process" nil
(cadr oa) params))
(kill-buffer nil)
(throw 'openwith-done t))))
The in your case oa would have the following structure, and the cadr is "xfig":
(cadr '("\.fig\'" "xfig" (file))) ;; expands to => xfig
This is the definition and doc of start-process:
Function: start-process name buffer-or-name program &rest args
http://www.gnu.org/software/emacs/elisp/html_node/Asynchronous-Processes.html
args, are strings that specify command line arguments for the program.
An example:
(start-process "my-process" "foo" "ls" "-l" "/user/lewis/bin")
Now we need to figure out how params is constructed. With your example the argument to the mapcar is:
(nth 2 '("\.fig\'" "xfig" (file))) ;=> (file)
By the way you can write such lines in the scratch buffer in emacs and run them with C-M-x.
The (car args) refers to the parameter you give to openwith-association, note how the occurance of 'file in (nth 2 oa) is replaced by that. I'll just replace it with "here.txt" for now:
(mapcar (lambda (x)
(if (eq x 'file)
"here.txt"
(format "%s" x))) (nth 2 '("\.fig\'" "xfig" (file)))) ;=> ("here.txt")
Okay, now we see how the argument should be constructed:
(mapcar (lambda (x)
(if (eq x 'file)
"here.txt"
(format "%s" x)))
(nth 2 '("\.fig\'" "xfig"
("-specialtext" "-latexfont" "-startlatexFont" "default" file))))
; => ("-specialtext" "-latexfont" "-startlatexFont" "default" "here.txt")
Try this:
(setq openwith-associations
'(("\\.fig\\'" "xfig" ("-specialtext" "-latexfont" "-startlatexFont" "default" file))))
You have to supply each word as a single string in the list of parameters.