In emacs flyspell mode, sometimes the list of suggestions is really long, so that the pop-up menu is taller than the screen and requires vertical scrolling of the menu because the "Save word" option is at the bottom of the menu.
Is there a way to to do one of the following:
Move "Save word" to the top of the menu?
Put a hard limit on the number of suggestions displayed?
Modify the threshold of a matching word?
This code will limit all ispell solutions to a maximum of limit-ispell-choices-to, which gets the desired limit in flyspell.
(defvar limit-ispell-choices-to 5
"Number indicating the maximum number of choices to present")
(defadvice ispell-parse-output (after limit-ispell-choices activate)
(when (and (listp ad-return-value)
ad-return-value)
(let* ((miss-list-end (nthcdr (- limit-ispell-choices-to 1)
(nth 2 ad-return-value)))
(guess-list-end (nthcdr (- limit-ispell-choices-to 1)
(nth 3 ad-return-value))))
(when miss-list-end (setcdr miss-list-end nil))
(when guess-list-end (setcdr guess-list-end nil)))))
This will put "Save word" at the top. I just swapped two words in the source.
It's a bit of a hack, but I can't see a better way.
(defun flyspell-emacs-popup (event poss word)
"The Emacs popup menu."
(unless window-system
(error "This command requires pop-up dialogs"))
(if (not event)
(let* ((mouse-pos (mouse-position))
(mouse-pos (if (nth 1 mouse-pos)
mouse-pos
(set-mouse-position (car mouse-pos)
(/ (frame-width) 2) 2)
(mouse-position))))
(setq event (list (list (car (cdr mouse-pos))
(1+ (cdr (cdr mouse-pos))))
(car mouse-pos)))))
(let* ((corrects (if flyspell-sort-corrections
(sort (car (cdr (cdr poss))) 'string<)
(car (cdr (cdr poss)))))
(cor-menu (if (consp corrects)
(mapcar (lambda (correct)
(list correct correct))
corrects)
'()))
(affix (car (cdr (cdr (cdr poss)))))
show-affix-info
(base-menu (let ((save (if (and (consp affix) show-affix-info)
(list
(list (concat "Save affix: " (car affix))
'save)
'("Accept (session)" session)
'("Accept (buffer)" buffer))
'(("Save word" save)
("Accept (session)" session)
("Accept (buffer)" buffer)))))
(if (consp cor-menu)
(append save (cons "" cor-menu))
save)))
(menu (cons "flyspell correction menu" base-menu)))
(car (x-popup-menu event
(list (format "%s [%s]" word (or ispell-local-dictionary
ispell-dictionary))
menu)))))
Related
I would like to extract the processing instructions (particularly xml-model) from an XML file; yet both (n)xml-parse-file as well as libxml-parse-xml-region do not recognize processing instructions.
Is there a clean way to extract processing instructions or do I have to regex search for PIs?
edit: Here is a first draft of the functionality I was looking for:
(cl-defun extract-processing-instructions (&rest processing-instructions)
"Extracts all/only the specified xml processing instructions from the current buffer and returns them as a list of string."
(interactive)
(let ((pi-re
(format "<\\?\\(%s\\).*\\?>" (string-join processing-instructions "\\|")))
(result))
(save-excursion
(goto-char (point-min))
(while (re-search-forward pi-re nil t)
(push (match-string 0) result)))
(nreverse result)))
(cl-defun pi-str2sexp (pi-str)
"Takes a processing instruction as a string and transforms it to a sexp-structure (in the style of xml-parse-*)."
(let (sexp attr-alist)
(save-match-data
;; get and push pi-element-name
;; (string-match "<\\?\\([[:alnum:]-]*\\)" pi-str)
(string-match "<\\?\\([[:alnum:]-]*\\)" pi-str)
(push (make-symbol (match-string 1 pi-str)) sexp)
;; construct attribute alist
(while (string-match "\\([[:alnum:]-]*\\)=\"\\([^ ]*\\)\""
pi-str (match-end 0))
(push (cons (make-symbol (match-string 1 pi-str))
(match-string 2 pi-str))
attr-alist)))
;; finally: push attr alist and return sexp
(push (nreverse attr-alist) sexp)
(nreverse sexp)))
edit 2: Turns out advicing/generally building upon xml-parse-* in this matter (like suggested by #Tom Regner) is a huge pain. :(
The thing I came up with was a context manager, the idea was to use it to around-advice string-parse-tag-1 (which is at the heart of xml-parse-* (of course stand-alone use is also an option):
(cl-defmacro --replace-first-group (regex-replace-alist)
`(save-excursion
(dolist (expression ,regex-replace-alist)
(goto-char (point-min))
(replace-regexp (car expression) (cadr expression)))))
(cl-defmacro with-parsable-pi (buffer &body body)
"Context manager that treats xml processing instructions in BUFFER as normal elements."
(declare (indent defun))
`(let ((old-buffer ,buffer))
(with-temp-buffer
(insert-buffer-substring old-buffer)
(goto-char (point-min))
(--replace-first-group '(("\\(\\?\\)>" "/>") ("<\\(\\?\\)" "<")))
,#body)))
This e.g. allows calls like
(with-parsable-pi (current-buffer)
(xml-parse-tag-1))
so it is at least possible to get an element at a time; but since the XML exposed in the context manager isn't actually valid and xml-parse-* (rightfully) errors if invalid XML is encountered, it isn't possible to process more than one element at a time.
I was thinking of maybe introducing a pseudo root element or something, but the kludge spiral is ghastly enough as it is.
Another idea of course would be to run an xpath query to extract processing instructions. If there only was a solid xpath solution in Emacs Lisp..
Ok, I think I found a satisfactory solution: xmltok-forward-prolog!
So here is the code I came up with for extracting processing instructions:
(cl-defun filter-xmltok-prolog (&optional (buffer (current-buffer))
(filter-re "processing-instruction-.*"))
"Filters the output of xmltok-forward-prolog (i.e. index 0 ('type') of each array) run in the context of BUFFER against FILTER-RE. Returns a list of vectors."
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(let ((raw-prolog-data (xmltok-forward-prolog)))
(seq-filter
#'(lambda (x)
(string-match filter-re (symbol-name (aref x 0))))
raw-prolog-data)))))
(cl-defun --merge-pi-data (pi-data)
"Meant to operate on data filtered with filter-xmltok-prolog against 'processing-instruction-.*'.
Merges processing-instruction-left/-right and returns a list of vectors holding the start/end coordinates of a processing instruction at index 1 and 2."
(let ((left (car pi-data))
(right (cadr pi-data)))
(cond
((null pi-data) nil)
(t (cons
(vector 'processing-instruction
(aref left 1) (aref right 2))
(--merge-pi-data (cddr pi-data)))))))
;; test
(--merge-pi-data '([processing-instruction-left 40 51] [processing-instruction-right 52 126]))
(cl-defun pi-str2s-exp (pi-str)
"Takes a processing instruction as a string and transforms it into a sexp structure (in the style of xml-parse-*)."
(let (sexp attr-alist)
(save-match-data
;; get and push pi-element-name
(string-match "<\\?\\([[:alnum:]-]*\\)" pi-str)
(push (make-symbol (match-string 1 pi-str)) sexp)
;; construct attribute alist
(while (string-match "\\([[:alnum:]-]*\\)=\"\\([^ ]*\\)\""
pi-str (match-end 0))
(push (cons (make-symbol (match-string 1 pi-str))
(match-string 2 pi-str))
attr-alist)))
;; finally: push attr alist and return sexp
(push (nreverse attr-alist) sexp)
(nreverse sexp)))
(cl-defun get-processing-instructions (&optional (buffer (current-buffer)))
"Extracts processing instructions from BUFFER and returns a list of sexp representations in the style of xml-parse-*."
(save-excursion
(mapcar #'pi-str2s-exp
(mapcar #'(lambda (v)
(buffer-substring (aref v 1) (aref v 2)))
(--merge-pi-data (filter-xmltok-prolog buffer))))))
(cl-defun test/get-pis-from-file (file)
(with-temp-buffer
(insert-file-contents file)
(get-processing-instructions)))
(test/get-pis-from-file "~/some/xml/file.xml")
I'm not at all an Emacs Lisp expert and this isn't at all tested thoroughly, but it works for now! :)
How can I cycle through only those buffers which are in a given major mode (such as Python-mode ) ?
Currently I am using C-X-Left/Right arrows, but these also show all sorts of irrelevant (i.e. non source code) buffers, any idea how can I restrict the buffer switching only to a specific type of buffer (with a given major mode) ?
I could not find something ready-made. However, it is not very hard to make the suitable commands.
(defun buffer-mode-alist ()
"Returns a list of (<buffer-name> . <major-mode>) pairs."
(let ((all-buffers (buffer-list))
(rv nil))
(while all-buffers
(let* ((this (car all-buffers))
(name (buffer-name this)))
(setq all-buffers (cdr all-buffers))
(when name
(setq rv (cons (cons name (with-current-buffer this major-mode)) rv)))))
rv))
(defun buffers-with-major-mode (the-major-mode)
(let ((buffer-alist (buffer-mode-alist))
(rv nil))
(while buffer-alist
(let ((this (car buffer-alist)))
(setq buffer-alist (cdr buffer-alist))
(if (eql (cdr this) the-major-mode)
(setq rv (cons (car this) rv)))))
(sort rv #'string<)))
(defun spin-buffers (buffer-list current)
(cond ((not (member current buffer-list)) buffer-list)
((string= current (car buffer-list)) buffer-list)
(t (spin-buffers (append (cdr buffer-list)
(list (car buffer-list)))
current))))
(defvar next-buffer-mode nil)
(defun choose-next-buffer-mode (mode)
"Ask for what major mode should be used as a selector for next-buffer-with-mode."
(interactive "aMajor Mode: ")
(setq next-buffer-mode mode))
(defun next-buffer-with-mode (set)
"Switches to the 'next' buffer with a given mode. If the mode is not set,
require it to be set, by calling choose-next-buffer-mode. If any prefix
argument is passed, also call choose-next-buffer-mode."
(interactive "P")
(when (or (not next-buffer-mode)
set)
(call-interactively 'choose-next-buffer-mode))
(let ((buffers (spin-buffers (buffers-with-major-mode next-buffer-mode)
(buffer-name))))
(when (cdr buffers)
(switch-to-buffer (cadr buffers)))))
(defun prev-buffer-with-mode (set)
"Switches to the 'previous' buffer with a given mode. If the mode is not set,
require it to be set, by calling choose-next-buffer-mode. If any prefix
argument is passed, also call choose-next-buffer-mode."
(interactive "P")
(when (or (not next-buffer-mode)
set)
(call-interactively 'choose-next-buffer-mode))
(let ((buffers (spin-buffers (buffers-with-major-mode next-buffer-mode)
(buffer-name))))
(when buffers
(switch-to-buffer (car (last buffers))))))
Does anyone have any ideas, please, regarding how to specify (within a function) switching to a specific tab group using Tabbar 2.0 and a current version of Emacs? For example, If the sky is blue, then switch to tab group "BLUE" (and/or the most recently viewed tab / buffer within that particular tab group).
I have written a few functions that permit me to organize tab groups by frames such that the tabs appear to be associated with a given frame. However, my function cycles through the various tab groups using tabbar-forward-group until the function finally stops at the correct group -- this method is very slow.
The function tabbar-current-tabset is used to determine the name of a current tab group that has focus. The result can be seen when placing it inside a message -- e.g., (message "%s" tabbar-current-tabset). It can also be used inside a function such as . . . (if (not (equal (format "%s" tabbar-current-tabset) "common")) . . . (tabbar-forward-group).
There is only one working function I have found that permits selecting a specific tab group, which is called ido-jump-to-tab-group (set forth below): https://github.com/bamanzi/dotemacs-full/blob/master/init.d/25-tabbar.el I am looking for a way to select a specific tab group (hard-coded into the function), without pausing to manually choose it using ido . . .. I mention this because it may help someone to resolve: If the sky is blue, then switch to tab group "BLUE" (and/or the most recently viewed tab / buffer within that particular tab group).
(defun ido-jump-to-tab-group ()
"Jump to a tabbar group."
(interactive)
(if (< emacs-major-version 24)
(ido-common-initialization))
(unless (and (featurep 'tabbar)
tabbar-mode)
(error "Error: tabbar-mode not turned on."))
(set tabbar-tabsets-tabset (tabbar-map-tabsets 'tabbar-selected-tab)) ;; refresh groups
(let* ( (groups (mapcar #'(lambda (group)
(format "%s" (cdr group)))
(tabbar-tabs tabbar-tabsets-tabset)))
(group-name (ido-completing-read "Groups: " groups)) )
(mapc #'(lambda (group)
(when (string= group-name (format "%s" (cdr group)))
(message "Switch to group '%s', current buffer: %s" (cdr group) (car group))
(switch-to-buffer (car group)) ))
(tabbar-tabs tabbar-tabsets-tabset))) )
During my Google searches, I came across an apparently broken function that does not work with Tabbar 2.0 and a current version of Emacs Trunk -- it is called tabbar+switch-group: https://gist.github.com/Johniel/4324127 I mention this function because it is the only one (other than ido-jump-to-tab-group) that is related to this issue.
(defun goto-tab-group (group-name)
"Jump to a specific tabbar group."
(unless (and (featurep 'tabbar)
tabbar-mode)
(error "Error: tabbar-mode not turned on."))
(set tabbar-tabsets-tabset (tabbar-map-tabsets 'tabbar-selected-tab)) ;; refresh groups
(let* ( (groups (mapcar #'(lambda (group)
(format "%s" (cdr group)))
(tabbar-tabs tabbar-tabsets-tabset))))
(mapc #'(lambda (group)
(when (string= group-name (format "%s" (cdr group)))
(message "Switch to group '%s', current buffer: %s" (cdr group) (car group))
(switch-to-buffer (car group)) ))
(tabbar-tabs tabbar-tabsets-tabset))) )
(defun example-using-goto-tab-group ()
(interactive)
(goto-tab-group "BLUE")) ;; predefined existing tab group
EDIT (September 27, 2014): The function ido-jump-to-tab-group (in the question above) and the function goto-tab-group (in the answer immediately above) are not compatible with a custom tabbar-buffer-groups-function that groups tabs based upon buffers associated with a particular frame, with that special list imbedded in the frame-parameter (independent of the general buffer-list and general buried-buffer-list). The following functions fix that imcompatibility.
(defun ido-switch-tab-group ()
"Switch tab groups using ido."
(interactive)
(let* (
(tab-buffer-list (mapcar
#'(lambda (b)
(with-current-buffer b
(list (current-buffer)
(buffer-name)
(funcall tabbar-buffer-groups-function) )))
(funcall tabbar-buffer-list-function)))
(groups (delete-dups
(mapcar #'(lambda (group)
(car (car (cdr (cdr group))))) tab-buffer-list)))
(group-name (ido-completing-read "Groups: " groups)) )
(catch 'done
(mapc
#'(lambda (group)
(when (equal group-name (car (car (cdr (cdr group)))))
(throw 'done (switch-to-buffer (car (cdr group))))))
tab-buffer-list) )))
(defun switch-tab-group (group-name)
"Switch to a specific tab group."
(let ((tab-buffer-list (mapcar
#'(lambda (b)
(with-current-buffer b
(list (current-buffer)
(buffer-name)
(funcall tabbar-buffer-groups-function) )))
(funcall tabbar-buffer-list-function))))
(catch 'done
(mapc
#'(lambda (group)
(when (equal group-name (format "%s" (car (car (cdr (cdr group))))))
(throw 'done (switch-to-buffer (car (cdr group))))))
tab-buffer-list) )))
(defun switch-to-tab-group-n ()
"Switch to a predefined existing tab group named `N`."
(interactive)
(switch-tab-group "N"))
(defun switch-to-tab-group-a ()
"Switch to a predefined existing tab group named `A`."
(interactive)
(switch-tab-group "A"))
I'd like to create a function that offers me numbered or lettered choices (1, 2, 3, or a, b, c) of available frames to switch to, instead of manually typing the name. Aspell would be the closest example I can think of.
Could someone please share an example of how this might be done? Lines 6 to 14 of the following function creates a list of all available frame names on the fly. Additional functions related to frame switching can be found here
(defun switch-frame (frame-to)
(interactive (list (read-string (format "From: (%s) => To: %s. Select: "
;; From:
(frame-parameter nil 'name)
;; To:
(mapcar
(lambda (frame) "print frame"
(reduce 'concat
(mapcar (lambda (s) (format "%s" s))
(list "|" (frame-parameter frame 'name) "|" )
)
)
)
(frame-list) )
)))) ;; end of interactive statement
(setq frame-from (frame-parameter nil 'name))
(let ((frames (frame-list)))
(catch 'break
(while frames
(let ((frame (car frames)))
(if (equal (frame-parameter frame 'name) frame-to)
(throw 'break (select-frame-set-input-focus frame))
(setq frames (cdr frames)))))) )
(message "Switched -- From: \"%s\" To: \"%s\"." frame-from frame-to) )
EDIT (November 13, 2014): Here is a revised function using ido-completing-read:
(defun ido-switch-frame ()
(interactive)
(when (not (minibufferp))
(let* (
(frames (frame-list))
(frame-to (ido-completing-read "Select Frame: "
(mapcar (lambda (frame) (frame-parameter frame 'name)) frames))))
(catch 'break
(while frames
(let ((frame (car frames)))
(if (equal (frame-parameter frame 'name) frame-to)
(throw 'break (select-frame-set-input-focus frame))
(setq frames (cdr frames)))))))))
I see what you're trying to do. Here's how I've solved this problem:
Part 1
The files that you use every day should be bookmarked.
The reason is that you loose focus when you're reading any sort of menu,
even as short as you describe. After some time with bookmarks,
it becomes like touch-typing: you select the buffer without thinking about it.
You can check out this question
to see my system.
I've got about 20 important files and buffers bookmarked and reachable
in two keystrokes, e.g. μ k for keys.el and μ h for hooks.el.
A nice bonus is that bookmark-bmenu-list shows all this stuff, so I can
add/remove bookmarks easily
rename bookmarks (renaming changes binding)
it's clickable with mouse (sometimes useful)
bookmark+ allows function bookmarks, so I've got org-agenda on μ a
and magit on μ m.
And of course the dired bookmarks: source is on μ s and
org-files are on μ g.
Part 2
For the files that can't be bookmarked, I'm using:
(ido-mode)
(setq ido-enable-flex-matching t)
(global-set-key "η" 'ido-switch-buffer)
This is fast as well: you need one keystroke to call ido-switch-buffer
and around 2-3 letters to find the buffer you need, and RET to select.
I've also recently added this hack:
(add-hook 'ido-setup-hook
(lambda()
(define-key ido-buffer-completion-map "η" 'ido-next-match)))
With this you can use the same key to call ido-switch-buffer and cycle the selection.
Part 3
The actual function with lettered choices has been on my todo list for a while
now. I'll post back here when I get around to implementing it,
or maybe just copy the solution from a different answer:)
This answer describes Icicles command icicle-select-frame, which lets you choose frames by name using completion.
There is also Icicles command icicle-other-window-or-frame (C-x o), which combines commands icicle-select-frame, other-frame, and other-window. It lets you select a window or a frame, by its name or by order.
With no prefix arg or a non-zero numeric prefix arg:
If the selected frame has multiple windows then this is
other-window. Otherwise, it is other-frame.
With a zero prefix arg (e.g. C-0):
If the selected frame has multiple windows then this is
icicle-select-window with windows in the frame as candidates.
Otherwise (single-window frame), this is icicle-select-frame.
With plain C-u:
If the selected frame has multiple windows then this is
icicle-select-window with windows from all visible frames as
candidates. Otherwise, this is icicle-select-frame.
Depending upon the operating system, it may be necessary to use (select-frame-set-input-focus chosen-frame) instead of select-frame / raise-frame towards the end of the function.
(defface frame-number-face
'((t (:background "black" :foreground "red" )))
"Face for `frame-number-face`."
:group 'frame-fn)
(defface frame-name-face
'((t ( :background "black" :foreground "ForestGreen")))
"Face for `frame-name-face`."
:group 'frame-fn)
(defun select-frame-number ()
"Select a frame by number -- a maximum of 9 frames are supported."
(interactive)
(let* (
choice
chosen-frame
(n 0)
(frame-list (frame-list))
(total-frames (safe-length frame-list))
(frame-name-list
(mapcar
(lambda (frame) (cons frame (frame-parameter frame 'name)))
frame-list))
(frame-name-list-sorted
(sort
frame-name-list
#'(lambda (x y) (string< (cdr x) (cdr y)))))
(frame-number-list
(mapcar
(lambda (frame)
(setq n (1+ n))
(cons n (cdr frame)))
frame-name-list-sorted))
(pretty-list
(mapconcat 'identity
(mapcar
(lambda (x) (concat
"["
(propertize (format "%s" (car x)) 'face 'frame-number-face)
"] "
(propertize (format "%s" (cdr x)) 'face 'frame-name-face)))
frame-number-list)
" | ")) )
(message "%s" pretty-list)
(setq choice (read-char-exclusive))
(cond
((eq choice ?1)
(setq choice 1))
((eq choice ?2)
(setq choice 2))
((eq choice ?3)
(setq choice 3))
((eq choice ?4)
(setq choice 4))
((eq choice ?5)
(setq choice 5))
((eq choice ?6)
(setq choice 6))
((eq choice ?7)
(setq choice 7))
((eq choice ?8)
(setq choice 8))
((eq choice ?9)
(setq choice 9))
(t
(setq choice 10)))
(setq chosen-frame (car (nth (1- choice) frame-name-list-sorted)))
(when (> choice total-frames)
(let* (
(debug-on-quit nil)
(quit-message
(format "You must select a number between 1 and %s." total-frames)))
(signal 'quit `(,quit-message ))))
(select-frame chosen-frame)
(raise-frame chosen-frame)
chosen-frame))
The program should reformat the string like below.
Example: (game-print '(THIS IS A SENTENCE。 WHAT ABOUT THIS? PROBABLY.))
This is a sentence. What about ths? Probably.
But something is wrong( Lisp nesting exceeds `max-lisp-eval-depth) and i don't know why. This piece of code is actually from the book "Land of lisp" in page 97. The original code is written in common lisp. I want to rewrite it in elisp. The last two argument in tweak-text means captain and literal.
(defun tweak-text (lst caps lit)
(when lst
(let ((item (car lst))
(rest (cdr lst)))
(cond ((eql item ?\ ) (cons item (tweak-text rest caps lit)))
((member item '(?\! ?\? ?\.)) (cons item (tweak-text rest t lit)))
((eql item ?\") (tweak-text rest caps (not lit)))
(lit (cons item (tweak-text rest nil lit)))
(caps (cons (upcase item) (tweak-text rest nil lit)))
(t (cons (downcase item) (tweak-text rest nil nil)))))))
(defun game-print (lst)
(print (coerce (tweak-text (coerce (prin1-to-string lst) 'list) t nil) 'string)))
(game-print '(not only does this sentence have a "comma," it also mentions the "iPad."))
The orignal code written in common lisp.
(defun tweak-text (lst caps lit)
(when lst
(let ((item (car lst))
(rest (cdr lst)))
(cond ((eql item #\space) (cons item (tweak-text rest caps lit)))
((member item '(#\! #\? #\.)) (cons item (tweak-text rest t lit)))
((eql item #\") (tweak-text rest caps (not lit)))
(lit (cons item (tweak-text rest nil lit)))
(caps (cons (char-upcase item) (tweak-text rest nil lit)))
(t (cons (char-downcase item) (tweak-text rest nil nil)))))))
(defun game-print (lst)
(princ (coerce (tweak-text (coerce (string-trim "() " (prin1-to-string lst)) 'list) t nil) 'string))
(fresh-line))
In both cases, you have non-terminal recursions, so you're using
O(length(lst)) stack space. Obviously, systems may limit the stack
space you can use, and you do indeed reach this limit in emacs. (Now
then in emacs, you can increase the limit by changing
max-lisp-eval-depth, but this won't solve the fundamental problem).
The solution is to use iteration instead of recursion.
But first, write in emacs:
(defun character (x)
"common-lisp: return the character designated by X."
(etypecase x
(integer x)
(string (aref x 0))
(symbol (aref (symbol-name x) 0))))
(defun string-trim (character-bag string-designator)
"common-lisp: returns a substring of string, with all characters in \
character-bag stripped off the beginning and end."
(unless (sequencep character-bag)
(signal 'type-error "expected a sequence for `character-bag'."))
(let* ((string (string* string-designator))
(margin (format "[%s]*" (regexp-quote
(if (stringp character-bag)
character-bag
(map 'string 'identity character-bag)))))
(trimer (format "\\`%s\\(\\(.\\|\n\\)*?\\)%s\\'" margin margin)))
(replace-regexp-in-string trimer "\\1" string)))
(require 'cl)
so that you can write a single function for both CL and elisp:
(defun tweak-text (list caps lit)
(let ((result '()))
(dolist (item list (nreverse result))
(cond ((find item " !?.") (push item result))
((eql item (character "\"")) (setf lit (not lit)))
(lit (push item result)
(setf caps nil))
(caps (push (char-upcase item) result)
(setf caps nil))
(t (push (char-downcase item) result)
(setf caps nil
lit nil))))))
(defun game-print (list)
(princ (coerce (tweak-text (coerce (string-trim "() " (prin1-to-string list)) 'list)
t nil)
'string))
(terpri))
Then:
(game-print '(not only does this sentence have a "comma," it also mentions the "iPad."))
in emacs:
prints: Not only does this sentence have a comma, it also mentions the iPad.
returns: t
in Common Lisp:
prints: Not only does this sentence have a comma, it also mentions the iPad.
returns: nil
Now, in general there's little point of using lists to process strings, both emacs lisp and Common Lisp have powerful primitives to deal with sequences and strings directly.
Note that elisp (sadly) does not optimise for tail-recursion, so that turns out to be a very inefficient way to write this function.
You are indeed hitting the 'max-lisp-eval-depth' limit when recursing in tweak-text. I don't see anything wrong with the way the code is(I didn't check if its doing what you intend it to do).
You can configure/raise the 'max-lisp-eval-depth' limit. The documentation for that variable states that you can raise it as long as you are confident that you are not going to trip into running out of stack space. The limit is conservatively set to 541 on my machine. Raising it to 600 got the function definition above to work on the input you gave as example.
As Pascal Bourguignon already mentioned it, using strings w/o coercing them to lists and back would be a better approach, below is my take at it. Note that it is slightly different in that literal strings are verified for punctuation, and if they appear to have punctuation such as would cause it otherwise to have the next letter upper-cased, then it would be upper cased too. I'm not sure this is a disadvantage, this is why I didn't take care of this difference.
(defun tweak-text (source)
(let ((i 0) (separator "") (cap t) current)
(with-output-to-string
(dolist (i source)
(setq current
(concat separator
(etypecase i
(string i)
(symbol (downcase (symbol-name i)))))
separator " ")
(let (current-char)
(dotimes (j (length current))
(setq current-char (aref current j))
(cond
((position current-char " \t\n\r"))
(cap (setq cap nil
current-char (upcase current-char)))
((position current-char ".?!")
(setq cap t)))
(princ (char-to-string current-char))))))))
(tweak-text '(not only does this sentence have a "comma," it also mentions the "iPad."))
"Not only does this sentence have a comma, it also mentions the iPad."
I think you should write something like this:
(defun tweak-text-wrapper (&rest args)
(let ((max-lisp-eval-depth 9001)) ; as much as you want
(apply tweak-text args)))