Is there a way to tweak the alignment rules in c-derived modes, in my case d-mode to indent D-style UFCS-Chains such as
foreach (file; dirPath.expandTilde()
.buildNormalizedPath()
.dirEntries(SpanMode.shallow)()
In this case I would like to align on the point, that is.
For details see https://github.com/Emacs-D-Mode-Maintainers/Emacs-D-Mode/issues/26
You have to modify the value of arglist-cont-nonempty key in your c-offsets-alist. Also, you will probably want to modify the statement-cont key to enable the same indentation in general statements (e.g. assignments) as well:
(add-to-list 'c-offsets-alist '(arglist-cont-nonempty . c-lineup-cascaded-calls))
(add-to-list 'c-offsets-alist '(statement-cont . c-lineup-cascaded-calls))
Obviously, you can use something like:
(add-hook 'd-mode-hook
'(lambda ()
(add-to-list 'c-offsets-alist '(arglist-cont-nonempty . c-lineup-cascaded-calls))
(add-to-list 'c-offsets-alist '(statement-cont . c-lineup-cascaded-calls))))
to enable this alignment in every d-mode buffer.
If you want to account for optional parenthesis, I believe you'll have to write your own "lining-up" function, since I can't think of a built-in solution. Here's a dirty rewrite of c-lineup-cascaded-calls:
(defun d-lineup-cascaded-calls (langelem)
"This is a modified `c-lineup-cascaded-calls' function for the
D programming language which accounts for optional parenthesis
and compile-time parameters in function calls."
(if (and (eq (c-langelem-sym langelem) 'arglist-cont-nonempty)
(not (eq (c-langelem-2nd-pos c-syntactic-element)
(c-most-enclosing-brace (c-parse-state)))))
;; The innermost open paren is not our one, so don't do
;; anything. This can occur for arglist-cont-nonempty with
;; nested arglist starts on the same line.
nil
(save-excursion
(back-to-indentation)
(let ((operator (and (looking-at "\\.")
(regexp-quote (match-string 0))))
(stmt-start (c-langelem-pos langelem)) col)
(when (and operator
(looking-at operator)
(or (and
(zerop (c-backward-token-2 1 t stmt-start))
(eq (char-after) ?\()
(zerop (c-backward-token-2 2 t stmt-start))
(looking-at operator))
(and
(zerop (c-backward-token-2 1 t stmt-start))
(looking-at operator))
(and
(zerop (c-backward-token-2 1 t stmt-start))
(looking-at operator))
)
)
(setq col (current-column))
(while (or (and
(zerop (c-backward-token-2 1 t stmt-start))
(eq (char-after) ?\()
(zerop (c-backward-token-2 2 t stmt-start))
(looking-at operator))
(and
(zerop (c-backward-token-2 1 t stmt-start))
(looking-at operator))
(and
(zerop (c-backward-token-2 1 t stmt-start))
(looking-at operator))
)
(setq col (current-column)))
(vector col))))))
It seems to work for the optional parenthesis and for double parenthesis call (i.e. call!(type)(arg) ) as well. But maybe there are some corner cases, so don't count on it too much, it's just an idea to work with.
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! :)
I have one org mode document that includes other org mode documents. The parent document should be exportable to pdf and each of the children should be aswell. Here is an example:
index.org
#+TITLE: Test Title
* Intro
This file must be exportable
* Heading 1
#+INCLUDE: doc1.org :minlevel 2 :only-contents t
doc1.org
#+TITLE: Inner title
This file must be exportable by itself aswell
* Heading 2
And here is some text
Exporting doc1.org produces the expected:
But exporting index.org yields (notice the title):
Is there a way to suppress the export options of included org documents?
The #+INCLUDE mechanism can include a portion of the file, so you can say
#+INCLUDE: doc1.org :minlevel 2 :only-contents t :lines "2-"
and have it skip the #+TITLE line in the included file. See http://orgmode.org/org.html#Include-files.
I made this ugly fix by overriding some org mode elisp. I put this in my .emacs and now things work as expected. Maybe I will post a patch to org mode when I get the time.
(defun fd--org-doc-begin ()
"Skip all the initial export options"
(save-excursion
(goto-char (point-min))
(while (and (or
(looking-at "[ \t]*#+")
(looking-at "[ \t]*$"))
(progn (next-line) (< (point) (point-max))))
(beginning-of-line))
(point)))
;;; This was overriden from ox.el
(defun org-export--prepare-file-contents
(file &optional lines ind minlevel id footnotes with-export-options)
"Prepare contents of FILE for inclusion and return it as a string.
When optional argument LINES is a string specifying a range of
lines, include only those lines.
Optional argument IND, when non-nil, is an integer specifying the
global indentation of returned contents. Since its purpose is to
allow an included file to stay in the same environment it was
created (e.g., a list item), it doesn't apply past the first
headline encountered.
Optional argument MINLEVEL, when non-nil, is an integer
specifying the level that any top-level headline in the included
file should have.
Optional argument ID is an integer that will be inserted before
each footnote definition and reference if FILE is an Org file.
This is useful to avoid conflicts when more than one Org file
with footnotes is included in a document.
Optional argument FOOTNOTES is a hash-table to store footnotes in
the included document.
Optional argument WITH-EXPORT-OPTIONS will stop this function
from ignoring export options at the beginning of the file."
(with-temp-buffer
(insert-file-contents file)
(when (not with-export-options)
(narrow-to-region (fd--org-doc-begin) (point-max)))
(when lines
(let* ((lines (split-string lines "-"))
(lbeg (string-to-number (car lines)))
(lend (string-to-number (cadr lines)))
(beg (if (zerop lbeg) (point-min)
(goto-char (point-min))
(forward-line (1- lbeg))
(point)))
(end (if (zerop lend) (point-max)
(goto-char (point-min))
(forward-line (1- lend))
(point))))
(narrow-to-region beg end)))
;; Remove blank lines at beginning and end of contents. The logic
;; behind that removal is that blank lines around include keyword
;; override blank lines in included file.
(goto-char (point-min))
(org-skip-whitespace)
(beginning-of-line)
(delete-region (point-min) (point))
(goto-char (point-max))
(skip-chars-backward " \r\t\n")
(forward-line)
(delete-region (point) (point-max))
;; If IND is set, preserve indentation of include keyword until
;; the first headline encountered.
(when (and ind (> ind 0))
(unless (eq major-mode 'org-mode)
(let ((org-inhibit-startup t)) (org-mode)))
(goto-char (point-min))
(let ((ind-str (make-string ind ?\s)))
(while (not (or (eobp) (looking-at org-outline-regexp-bol)))
;; Do not move footnote definitions out of column 0.
(unless (and (looking-at org-footnote-definition-re)
(eq (org-element-type (org-element-at-point))
'footnote-definition))
(insert ind-str))
(forward-line))))
;; When MINLEVEL is specified, compute minimal level for headlines
;; in the file (CUR-MIN), and remove stars to each headline so
;; that headlines with minimal level have a level of MINLEVEL.
(when minlevel
(unless (eq major-mode 'org-mode)
(let ((org-inhibit-startup t)) (org-mode)))
(org-with-limited-levels
(let ((levels (org-map-entries
(lambda () (org-reduced-level (org-current-level))))))
(when levels
(let ((offset (- minlevel (apply #'min levels))))
(unless (zerop offset)
(when org-odd-levels-only (setq offset (* offset 2)))
;; Only change stars, don't bother moving whole
;; sections.
(org-map-entries
(lambda ()
(if (< offset 0) (delete-char (abs offset))
(insert (make-string offset ?*)))))))))))
;; Append ID to all footnote references and definitions, so they
;; become file specific and cannot collide with footnotes in other
;; included files. Further, collect relevant footnote definitions
;; outside of LINES, in order to reintroduce them later.
(when id
(let ((marker-min (point-min-marker))
(marker-max (point-max-marker))
(get-new-label
(lambda (label)
;; Generate new label from LABEL by prefixing it with
;; "-ID-".
(format "-%d-%s" id label)))
(set-new-label
(lambda (f old new)
;; Replace OLD label with NEW in footnote F.
(save-excursion
(goto-char (+ (org-element-property :begin f) 4))
(looking-at (regexp-quote old))
(replace-match new))))
(seen-alist))
(goto-char (point-min))
(while (re-search-forward org-footnote-re nil t)
(let ((footnote (save-excursion
(backward-char)
(org-element-context))))
(when (memq (org-element-type footnote)
'(footnote-definition footnote-reference))
(let* ((label (org-element-property :label footnote)))
;; Update the footnote-reference at point and collect
;; the new label, which is only used for footnotes
;; outsides LINES.
(when label
(let ((seen (cdr (assoc label seen-alist))))
(if seen (funcall set-new-label footnote label seen)
(let ((new (funcall get-new-label label)))
(push (cons label new) seen-alist)
(org-with-wide-buffer
(let* ((def (org-footnote-get-definition label))
(beg (nth 1 def)))
(when (and def
(or (< beg marker-min)
(>= beg marker-max)))
;; Store since footnote-definition is
;; outside of LINES.
(puthash new
(org-element-normalize-string (nth 3 def))
footnotes))))
(funcall set-new-label footnote label new)))))))))
(set-marker marker-min nil)
(set-marker marker-max nil)))
(org-element-normalize-string (buffer-string))))
I don't like how plists are indented in Elisp.
;; current desired Python (for comparison)
;; '(a 1 '(a 1 {'a': 1,
;; b 2 b 2 'b': 2,
;; c 3) c 3) 'c': 3}
Tried on M-x emacs-version 24.3.1, ran emacs -Q, typed the plist and pressed C-x h C-M-\.
This indentation makes sense when it isn't a list:
(mapcar (lambda (x) (x + 1))
'(1 2 3 4))
How do I change formatting settings so that only plists (or, if that's impossible, all quoted lists) have the desired rectangular indentation, but indentation of everything else stays the same? I need this stored locally in an .el file, so that when I edit this file, it is indented as desired, but this behavior doesn't end up anywhere else.
Found it:
(setq lisp-indent-function 'common-lisp-indent-function)
Here's a sample file:
(setq x '(a 1
b 2
c 3))
;;; Local Variables:
;;; lisp-indent-function: common-lisp-indent-function
;;; End:
I'll just dump my whole indentation config here:
(setq lisp-indent-function 'common-lisp-indent-function)
(put 'cl-flet 'common-lisp-indent-function
(get 'flet 'common-lisp-indent-function))
(put 'cl-labels 'common-lisp-indent-function
(get 'labels 'common-lisp-indent-function))
(put 'if 'common-lisp-indent-function 2)
(put 'dotimes-protect 'common-lisp-indent-function
(get 'when 'common-lisp-indent-function))
You can fix this (in my opinion) bug by overriding lisp-indent-function. The original source of the hack was this Github Gist, which was referenced with some more explanation from this Emacs Stack Exchange answer.
However, I was very uncomfortable overriding a core function like this. For one, it's very opaque—how is a reader supposed to tell what is changed? And worse—what if the official definition of lisp-indent-function changed in the future? How would I know that I needed to update my hack?
As a response, I created the library el-patch, which is specifically designed to address this problem. After installing the package, you can override lisp-indent-function as follows:
(el-patch-defun lisp-indent-function (indent-point state)
"This function is the normal value of the variable `lisp-indent-function'.
The function `calculate-lisp-indent' calls this to determine
if the arguments of a Lisp function call should be indented specially.
INDENT-POINT is the position at which the line being indented begins.
Point is located at the point to indent under (for default indentation);
STATE is the `parse-partial-sexp' state for that position.
If the current line is in a call to a Lisp function that has a non-nil
property `lisp-indent-function' (or the deprecated `lisp-indent-hook'),
it specifies how to indent. The property value can be:
* `defun', meaning indent `defun'-style
(this is also the case if there is no property and the function
has a name that begins with \"def\", and three or more arguments);
* an integer N, meaning indent the first N arguments specially
(like ordinary function arguments), and then indent any further
arguments like a body;
* a function to call that returns the indentation (or nil).
`lisp-indent-function' calls this function with the same two arguments
that it itself received.
This function returns either the indentation to use, or nil if the
Lisp function does not specify a special indentation."
(el-patch-let (($cond (and (elt state 2)
(el-patch-wrap 1 1
(or (not (looking-at "\\sw\\|\\s_"))
(looking-at ":")))))
($then (progn
(if (not (> (save-excursion (forward-line 1) (point))
calculate-lisp-indent-last-sexp))
(progn (goto-char calculate-lisp-indent-last-sexp)
(beginning-of-line)
(parse-partial-sexp (point)
calculate-lisp-indent-last-sexp 0 t)))
;; Indent under the list or under the first sexp on the same
;; line as calculate-lisp-indent-last-sexp. Note that first
;; thing on that line has to be complete sexp since we are
;; inside the innermost containing sexp.
(backward-prefix-chars)
(current-column)))
($else (let ((function (buffer-substring (point)
(progn (forward-sexp 1) (point))))
method)
(setq method (or (function-get (intern-soft function)
'lisp-indent-function)
(get (intern-soft function) 'lisp-indent-hook)))
(cond ((or (eq method 'defun)
(and (null method)
(> (length function) 3)
(string-match "\\`def" function)))
(lisp-indent-defform state indent-point))
((integerp method)
(lisp-indent-specform method state
indent-point normal-indent))
(method
(funcall method indent-point state))))))
(let ((normal-indent (current-column))
(el-patch-add
(orig-point (point))))
(goto-char (1+ (elt state 1)))
(parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
(el-patch-swap
(if $cond
;; car of form doesn't seem to be a symbol
$then
$else)
(cond
;; car of form doesn't seem to be a symbol, or is a keyword
($cond $then)
((and (save-excursion
(goto-char indent-point)
(skip-syntax-forward " ")
(not (looking-at ":")))
(save-excursion
(goto-char orig-point)
(looking-at ":")))
(save-excursion
(goto-char (+ 2 (elt state 1)))
(current-column)))
(t $else))))))
Here is another less heavyweight solution, based on emacsql-fix-vector-indentation. An advice around calculate-lisp-indent is sufficient.
This only works for plists that use keywords as keys, but that covers a majority of plists. To make this work on quoted lists instead, you could change the looking-at regexp to detect the ' or "`", but that will not cover, say, a nested list.
This can further be packaged up into a minor mode if there is a need to turn it off.
(defun my/inside-plist? ()
"Is point situated inside a plist?
We determine a plist to be a list that starts with a keyword."
(let ((start (point)))
(save-excursion
(beginning-of-defun)
(let ((sexp (nth 1 (parse-partial-sexp (point) start))))
(when sexp
(setf (point) sexp)
(looking-at (rx "(" (* (syntax whitespace)) ":")))))))
(define-advice calculate-lisp-indent (:around (func &rest args)
plist)
(if (save-excursion
(beginning-of-line)
(my/inside-plist?))
(let ((lisp-indent-offset 1))
(apply func args))
(apply func args)))
I'd like to be able to toggle the case of the letter under the point. To that end, I wrote this:
(defun toggle-case-next-letter ()
"Toggles the case of the next letter, then moves the point forward one character"
(interactive)
(let* ((p (point))
(upcased (upcasep (char-after)))
(f (if upcased 'downcase-region 'upcase-region)))
(progn
(f p (+ 1 p))
(forward-char))))
However, when I run it (I've bound it to M-#), I get progn: Symbol's function definition is void: f. I assume this means f isn't bound, but I'm not sure.
Upcasep is defined as:
(defun upcasep (c) (eq c (upcase c)))
Is the problem in the let binding, or something else? (Also, if there's a better way to do this, that'd be nice as well).
Note that originally I had (upcased (upcasep (buffer-substring-no-properties p (+ 1 p)))), which I've corrected to (upcased (upcasep (char-after)), because using upcasep as defined above is always nil for strings (so I couldn't downcase again).
You've got a typical case of lisp-1 / lisp-2 confusion. Here's a fix (just a funcall):
(defun toggle-case-next-letter ()
"Toggles the case of the next letter, then moves the point forward one character"
(interactive)
(let* ((p (point))
(upcased (char-upcasep (buffer-substring-no-properties p (+ 1 p))))
(f (if upcased 'downcase-region 'upcase-region)))
(progn
(funcall f p (+ 1 p))
(forward-char))))
And here's what I have:
(global-set-key (kbd "C->") 'upcase-word-toggle)
(global-set-key (kbd "C-z") 'capitalize-word-toggle)
(defun char-upcasep (letter)
(eq letter (upcase letter)))
(defun capitalize-word-toggle ()
(interactive)
(let ((start (car
(save-excursion
(backward-word)
(bounds-of-thing-at-point 'symbol)))))
(if start
(save-excursion
(goto-char start)
(funcall
(if (char-upcasep (char-after))
'downcase-region
'upcase-region)
start (1+ start)))
(capitalize-word -1))))
(defun upcase-word-toggle ()
(interactive)
(let ((bounds (bounds-of-thing-at-point 'symbol))
beg end
regionp)
(if (eq this-command last-command)
(setq regionp (get this-command 'regionp))
(put this-command 'regionp nil))
(cond
((or (region-active-p) regionp)
(setq beg (region-beginning)
end (region-end))
(put this-command 'regionp t))
(bounds
(setq beg (car bounds)
end (cdr bounds)))
(t
(setq beg (point)
end (1+ beg))))
(save-excursion
(goto-char (1- beg))
(and (re-search-forward "[A-Za-z]" end t)
(funcall (if (char-upcasep (char-before))
'downcase-region
'upcase-region)
beg end)))))
I couldn't get #abo-abo's answer working for me but using his comments I was able to google better and found the following at http://chneukirchen.org/dotfiles/.emacs
(defun chris2-toggle-case ()
(interactive)
(let ((char (following-char)))
(if (eq char (upcase char))
(insert-char (downcase char) 1 t)
(insert-char (upcase char) 1 t)))
(delete-char 1 nil)
(backward-char))
(global-set-key (kbd "M-#") 'chris2-toggle-case)
This answers the original question if you remove (backward-char).
I realize this is a very old question, but having stumbled upon the same problem recently, I'd like to suggest a simpler solution.
I start with a pure function for toggling character case, based on char code property inspection:
(cl-defun toggle-char-case (c)
(cl-case (get-char-code-property c 'general-category)
(Lu (downcase c))
(Ll (upcase c))
(t c)))
I then use it from within an interactive function operating at point:
(cl-defun toggle-char-case-at-point ()
(interactive)
(let ((new (toggle-char-case (char-after))))
(delete-char 1)
(insert new)))
I then bound this interactive function to a keybinding of my choice:
(global-set-key (kbd "C-M-c") 'toggle-char-case-at-point)
The way this function operates is, after toggling the case it advances the point by one. So calling it repeatedly will toggle the cases of a sequence of chars. One could make it keep the point unchanged - that would require adding (backward-char) to the body.
How can I configure emacs to work in the same way as other modern editors where pressing Alt+D or Alt+Backspace deletes either adjacent whitespaces or a single word? By default, emacs always deletes a word.
Trough some time of using Emacs I figured that even though I can alter the basic functionality, it usually doesn't pay off much in terms of efficiency. In fact, after I did it several times, I came to regret it and undid it. This is not true all of the time, some keybindings are really uncomfortable or rarely useful, but I don't think this is the case with how kill word works. In fact, I just now realized that: I did try the keybinding in Eclipse, but I've been using it with Emacs-style kebindings since forever...
Anyways, as I just said, before you are "fixing" that functionality, make sure it is really broken :) I never find myself needing the kind of function you describe, and maybe here's why:
M-SPC reduces the space between words to just one space. This is what I would've used if the point was between the words and I wanted to delete the extra space separating the words.
M-\ removes all horizontal space. This will join two words separated by space.
If what you are trying to achieve is some kind of "sparse" formatting, as in:
int foo = 42;
unsigned int bar = 43;
then there's M-xalign-regexp to do that.
I just never happen to have a) long consequent runs of whitepsace, unless it is the indentation, and in the case it is the indentation, TAB usually handles it better. b) even if there are long consequent runs of whitespace, I so rarely move the point by one character at a time, so it's hard to think of a situation where I'd find the point surrounded by several whitespaces. Things like Artist mode, or Dot diagrams come to mind, but it doesn't happen during code editing.
Finally, if you are trying to, well, let's say just edit an arbitrary text file and you want to add or remove horizontal space between words... Again, there's M-xalign-regexp to do that, or you could use commands that operate on rectangles, if those are several lines at the time. Well, Emacs will even recognize the ad hoc tabs and will try to align the text such as to match the last line before the point, when you hit TAB.
Finally, if for some reason I cannot fathom :) I really needed to do exactly what you describe, then I'd do it like so: kM-\BACKSPACE (it can be any other key instead of "k" - it is just right under your finger, so it's fast to type :) Or, if I'm lazy to think about it: M-SPCM-fM-bC-w - maybe sounds like a lot, but these are the commands you would be using all of the time anyway, so it doesn't hinder you in terms of speed.
(defvar movement-syntax-table
(let ((st (make-syntax-table)))
;; ` default = punctuation
;; ' default = punctuation
;; , default = punctuation
;; ; default = punctuation
(modify-syntax-entry ?{ "." st) ;; { = punctuation
(modify-syntax-entry ?} "." st) ;; } = punctuation
(modify-syntax-entry ?\" "." st) ;; " = punctuation
(modify-syntax-entry ?\\ "_" st) ;; \ = symbol
(modify-syntax-entry ?\$ "_" st) ;; $ = symbol
(modify-syntax-entry ?\% "_" st) ;; % = symbol
st)
"Syntax table used while executing custom movement functions.")
(defun delete-word-or-whitespace (&optional arg)
"http://stackoverflow.com/a/20456861/2112489"
(interactive "P")
(with-syntax-table movement-syntax-table
(let* (
beg
end
(word-regexp "\\sw")
(punctuation-regexp "\\s.")
(symbol-regexp "\\s_\\|\\s(\\|\\s)"))
(cond
;; Condition # 1
;; right of cursor = word or punctuation or symbol
((or
(save-excursion (< 0 (skip-syntax-forward "w")))
(save-excursion (< 0 (skip-syntax-forward ".")))
(save-excursion (< 0 (skip-syntax-forward "_()"))))
;; Condition #1 -- Step 1 of 2
(cond
;; right of cursor = word
((save-excursion (< 0 (skip-syntax-forward "w")))
(skip-syntax-forward "w")
(setq end (point))
(while (looking-back word-regexp)
(backward-char))
(setq beg (point))
(delete-region beg end))
;; right of cursor = punctuation
((save-excursion (< 0 (skip-syntax-forward ".")))
(skip-syntax-forward ".")
(setq end (point))
(while (looking-back punctuation-regexp)
(backward-char))
(setq beg (point))
(delete-region beg end))
;; right of cursor = symbol
((save-excursion (< 0 (skip-syntax-forward "_()")))
(skip-syntax-forward "_()")
(setq end (point))
(while (looking-back symbol-regexp)
(backward-char))
(setq beg (point))
(delete-region beg end)))
;; Condition #1 -- Step 2 of 2
(cond
;; right of cursor = whitespace
;; left of cursor = not word / not symbol / not punctuation = whitespace or bol
((and
(save-excursion (< 0 (skip-chars-forward "\s\t")))
(not (save-excursion (> 0 (skip-syntax-backward "w"))))
(not (save-excursion (> 0 (skip-syntax-backward "."))))
(not (save-excursion (> 0 (skip-syntax-backward "_()")))))
(setq beg (point))
(skip-chars-forward "\s\t")
(setq end (point))
(delete-region beg end))
;; right of cursor = whitespace
;; left of cursor = word or symbol or punctuation
((and
(save-excursion (< 0 (skip-chars-forward "\s\t")))
(or
(save-excursion (> 0 (skip-syntax-backward "w")))
(save-excursion (> 0 (skip-syntax-backward ".")))
(save-excursion (> 0 (skip-syntax-backward "_()")))))
(fixup-whitespace))))
;; Condition # 2
;; right of cursor = whitespace
;; left of cursor = bol | left of cursor = whitespace | right of cursor = whitespace + eol
((and
(save-excursion (< 0 (skip-chars-forward "\s\t")))
(or
(bolp)
(save-excursion (> 0 (skip-chars-backward "\s\t")))
(save-excursion (< 0 (skip-chars-forward "\s\t")) (eolp))))
(setq beg (point))
(skip-chars-forward "\s\t")
(setq end (point))
(delete-region beg end))
;; Condition # 3
;; right of cursor = whitespace or eol
;; left of cursor = word or symbol or punctuation
;; not bol + word or symbol or punctuation
;; not bol + whitespace + word or symbol or punctuation
((and
(or (save-excursion (< 0 (skip-chars-forward "\s\t"))) (eolp))
(or
(save-excursion (> 0 (skip-syntax-backward "w")))
(save-excursion (> 0 (skip-syntax-backward ".")))
(save-excursion (> 0 (skip-syntax-backward "_()"))))
(not (save-excursion (> 0 (skip-syntax-backward "w")) (bolp)))
(not (save-excursion (> 0 (skip-syntax-backward ".")) (bolp)))
(not (save-excursion (> 0 (skip-syntax-backward "_()")) (bolp)))
(not (save-excursion (and (> 0 (skip-syntax-backward "w")) (> 0 (skip-chars-backward "\s\t")) (bolp))))
(not (save-excursion (and (> 0 (skip-syntax-backward ".")) (> 0 (skip-chars-backward "\s\t")) (bolp))))
(not (save-excursion (and (> 0 (skip-syntax-backward "_()")) (> 0 (skip-chars-backward "\s\t")) (bolp)))))
(setq end (point))
(cond
((save-excursion (> 0 (skip-syntax-backward "w")))
(while (looking-back word-regexp)
(backward-char)))
((save-excursion (> 0 (skip-syntax-backward ".")))
(while (looking-back punctuation-regexp)
(backward-char)))
((save-excursion (> 0 (skip-syntax-backward "_()")))
(while (looking-back symbol-regexp)
(backward-char))))
(setq beg (point))
(when (save-excursion (> 0 (skip-chars-backward "\s\t")))
(skip-chars-backward "\s\t")
(setq beg (point)))
(delete-region beg end)
(skip-chars-forward "\s\t"))
;; Condition # 4
;; not bol = eol
;; left of cursor = bol + word or symbol or punctuation | bol + whitespace + word or symbol or punctuation
((and
(not (and (bolp) (eolp)))
(or
(save-excursion (> 0 (skip-syntax-backward "w")) (bolp))
(save-excursion (> 0 (skip-syntax-backward ".")) (bolp))
(save-excursion (> 0 (skip-syntax-backward "_()")) (bolp))
(save-excursion (and (> 0 (skip-syntax-backward "w")) (> 0 (skip-chars-backward "\s\t")) (bolp)))
(save-excursion (and (> 0 (skip-syntax-backward ".")) (> 0 (skip-chars-backward "\s\t")) (bolp)))
(save-excursion (and (> 0 (skip-syntax-backward "_()")) (> 0 (skip-chars-backward "\s\t")) (bolp)))))
(skip-chars-forward "\s\t")
(setq end (point))
(setq beg (point-at-bol))
(delete-region beg end))
;; Condition # 5
;; point = eol
;; not an empty line
;; whitespace to the left of eol
((and
(not (and (bolp) (eolp)))
(eolp)
(save-excursion (> 0 (skip-chars-backward "\s\t"))))
(setq end (point))
(skip-chars-backward "\s\t")
(setq beg (point))
(delete-region beg end))
;; Condition # 6
;; point = not eob
;; point = bolp and eolp
;; universal argument = C-u = '(4)
((and
(not (eobp))
(and (bolp) (eolp))
(equal arg '(4)))
(delete-forward-char 1))) )))
This has most likely been solved before, but instead of looking for code, we can write our own. So much fun!
This is how I would do it, hope it helps.
(defun kill-whitespace-or-word ()
(interactive)
(if (looking-at "[ \t\n]")
(let ((p (point)))
(re-search-forward "[^ \t\n]" nil :no-error)
(backward-char)
(kill-region p (point)))
(kill-word 1)))
Then bind it to a key:
(global-set-key (kbd "M-d") 'kill-whitespace-or-word)
If you are using a CC-Mode based buffer, you are probably looking for the Hungry Delete Mode minor mode.
Try C-c DEL and C-c DELETE in several places to get a feel for the difference.
If you like the way it works, you can toggle hungry deletion to work for the standard keys by doing M-x c-toggle-hungry-state or just rebind the hungry deletion functions to your preferred binding.
If you still think you need to piggyback one key to do forward kill word or whitespace, then you can do something similar to c-hungry-delete-forward, or just temporarily rebind c-delete-function and call it.
(defun c-hungry-delete-forward-word ()
"Delete the following word or all following whitespace
up to the next non-whitespace character.
See also \\[c-hungry-delete-backwards]."
(interactive)
(let ((c-delete-function (function kill-word)))
(c-hungry-delete-forward)))
Check out the Info page (ccmode) Hungry WS Deletion for more.