There are some use cases where the regex query
(query-replace-regexp "\\(^\\| *[^\\\\]\\)%.*" "" nil nil)
proposed by David Carlisle does not work for me.
Note: Below I write "slash" to mean "backward slash".
First issue
The regex matches a non-slash followed by a percent, hence:
123%foo
456
becomes:
12
456
Second issue
The % introducing a comment might be escaped, so it is not a comment, but the escape might be escaped as well, and so it is a comment. In short, we need to make sure that the comment is preceded by zero or an even number of slashes. It may seem convoluted, but consider this:
\\% <- Don't forget the newline
This comment is skipped by the regex, since the percent is considered escaped, but it is not.
In LaTeX, long sequences of slashes are everything but rare, so we need to manage parities.
Third issue
Comments eat the trailing newline. This feature is often used on purpose, for example in macros, where you want a line break for readability, but you don't want it to be used when substituting the macro. As it follows, for full-line comments it is better to remove the comment with trailing (or leading) newline, that is
123
%foo
456
should be
123
456
For inline comments, it is better to move the comment text, while leaving the new line, that is
123%foo
should be
123%
To overcome these issues, I suggest the following macro.
(defun no-coms ()
(interactive)
(while (search-forward-regexp "\\(\n?\\)\\(.*?\\)\\(\\\\*\\)\\(%.*\\)" nil t)
(when (cl-evenp (length (match-string 3))) ; bslahes should be even
;; Are we at bol?
(if (and (string-empty-p (match-string 2)) (string-empty-p (match-string 3)))
(replace-match "" nil nil nil 0) ; if so remove whole match
(replace-match "%" nil nil nil 4))))) ; else just remove comment text
Evaluate the macro, perhaps adding it to your init file, and after putting the cursor where you want the macro to start working, type
ALT+X no-coms
Note, I haven't tested this macro thoroughly.
Here are some explanations for the curious.
Removing ELisp escapes, the regex simplifies to:
(\n?)(.*?)(\\*)(%.*)
So it matches as subgroups: an optional newline, arbitrary characters non-greedily, zero or more slashes, a percent followed by arbitrary characters.
(while (search-forward-regexp ... keeps searching forward until the regexp finds a match.
(when (cl-evenp (length ... extracts the third subgroup match (the slashes) and proceeds only when the number of returned matches is even.
(if (string-empty-p (match-string 2))... checks if there are characters between the optional newline and the percent, that is if the second and third subgroup are both empty, in which case the percent is at the beginning of the line, and we have a full-line comment.
(replace-match "" nil nil nil 0) is the if affirmative case. We have a full-line comment, and we replace the entire match (the subgroup zero), which would include the initial newline.
(replace-match "%" nil nil nil 4) is the alternative case. Here the fourth match, the % followed by comment text, is replaced with the percent only.