Changing margin for emacs text-mode - emacs

The only way I found to change margins in emacs to my liking without things acting funny is this:
(add-hook 'window-configuration-change-hook
(lambda ()
(set-window-margins (car (get-buffer-window-list (current-buffer) nil t)) 24 24)))
I would like for this setting to be invoked only in text-mode and change back when I change to other modes. Somewhat naively I tried this:
(add-hook 'text-mode-hook
(lambda ()
(set-window-margins (car (get-buffer-window-list (current-buffer) nil t)) 24 24)))
But it's not working. What would be the right code to have the margins only change for buffers in text-mode?

Even though you can set the margins using set-window-margins, they are lost as soon as you change the window in any way. A better solution is to set the variables left-margin-width and right-margin-width. For example:
(defun my-set-margins ()
"Set margins in current buffer."
(setq left-margin-width 24)
(setq right-margin-width 24))
(add-hook 'text-mode-hook 'my-set-margins)

How about something like this? Your problem likely stems from the fact that many major modes inherit text-mode. You can either eliminate your window-configuration-change-hook, or you can use your new function major-briggs with it -- personally I'd just use the text-mode-hook with the major-briggs function.
(add-hook 'text-mode-hook (lambda ()
(major-briggs)
;; insert additional stuff if so desired
))
(defun major-briggs ()
(when (eq major-mode 'text-mode)
(set-window-margins
(car (get-buffer-window-list (current-buffer) nil t)) 24 24) ))

Here's some code to center your markdown and text files within 80 characters. This adjusts the margins for all your frames automatically.
;; Add left and right margins, when file is markdown or text.
(defun center-window (window) ""
(let* ((current-extension (file-name-extension (or (buffer-file-name) "foo.unknown")))
(max-text-width 80)
(margin (max 0 (/ (- (window-width window) max-text-width) 2))))
(if (and (not (string= current-extension "md"))
(not (string= current-extension "txt")))
;; Do nothing if this isn't an .md or .txt file.
()
(set-window-margins window margin margin))))
;; Adjust margins of all windows.
(defun center-windows () ""
(walk-windows (lambda (window) (center-window window)) nil 1))
;; Listen to window changes.
(add-hook 'window-configuration-change-hook 'center-windows)

With the following code:
(defvar-local my-text-width nil
"Text area width for current buffer, or nil if no attention needed.")
(put 'my-text-width 'safe-local-variable #'integerp)
(defun my--margin-setup ()
"Handle settings of `my-text-width'."
(walk-windows
(lambda (window)
(with-current-buffer (window-buffer window)
(let ((margin (and my-text-width
(/ (max 0 (- (window-total-width)
my-text-width))
2))))
(when (or (not (equal margin left-margin-width))
(not (equal margin right-margin-width)))
(setq left-margin-width margin)
(setq right-margin-width margin)
(set-window-buffer window (current-buffer))))))))
(add-hook 'window-configuration-change-hook #'my--margin-setup)
You can set my-text-width in a file-local or directory-local variable, and Emacs will automatically set the margins accordingly to get that text width. It works even when you resize the frame or introduce splits.

Related

How to keep proper visual alignment of marker with window size changes?

I have the following elisp function and its hook...
(defun org-fontify-proof-blocks ()
(interactive)
(add-to-list 'font-lock-extra-managed-props 'display)
(let ((margin-format (format "%%%ds" left-margin-width)))
(font-lock-add-keywords nil
`(
("^#\\+begin_proof.*?\\(\n\\)"
1 '(face nil display " "))
("^\\(#\\+begin_proof.*$\\)"
1 '(face nil display ,(propertize "Proof." 'face '(:inherit bold))))
("\\(#\\+end_proof.*\\)$"
1 '(face nil display ,(propertize (concat (make-string (- (window-width) (string-width "⏹")) ?\ ) "⏹") 'face '(:inherit bold))))
)))
(font-lock-fontify-buffer))
(add-hook 'org-mode-hook #'org-fontify-proof-blocks)
This results in the proof variant of the org-special-block, given by...
#+begin_proof
<contents>
#+end_proof
getting fontified as
Proof. <contents>
⏹
The problem is that the alignment of the QED symbold (⏹) gets messed up when changing window size. How can I go about fixing this issue?
I have tried
(add-hook 'window-size-change-functions #'org-fontify-proof-blocks)
which did not really seem to do anything.
I also tried
(add-hook 'window-configuration-change-hook
(lambda ()
(with-current-buffer (current-buffer)
(org-fontify-proof-blocks))))
and
(add-hook 'window-configuration-change-hook #'org-fontify-proof-blocks)
which only seemed to keep the improper alignment after switching back to the original window size.

Pin/lock/fix text

With elisp it is possible to add overlays to parts of the buffer to hide them etc. I went through all the possible overlays and couldn't find a way to pin a selection. Is it possible to have a function that, given a selection in a buffer, pins this selection so that when you scroll up or down the selection is always shown? (a bit like what you have with Excel where you can lock some rows or columns so that they always appear on screen).
I wanted to do something like this but with (overlay-put new-overlay 'lock t) but there doesn't appear to be such overlay.
(defun hide-region-hide ()
"Hides a region by making an invisible overlay over it and save the
overlay on the hide-region-overlays \"ring\""
(interactive)
(let ((new-overlay (make-overlay (mark) (point))))
(push new-overlay hide-region-overlays)
(overlay-put new-overlay 'invisible t)
(overlay-put new-overlay 'intangible t)
(overlay-put new-overlay 'before-string
(if hide-region-propertize-markers
(propertize hide-region-before-string
'font-lock-face 'hide-region-before-string-face)
hide-region-before-string))
(overlay-put new-overlay 'after-string
(if hide-region-propertize-markers
(propertize hide-region-after-string
'font-lock-face 'hide-region-after-string-face)
hide-region-after-string))))
I came up with this solution that works pretty well:
(defvar-local pinned-buffer nil
"Variable to store the buffer that contains the pinned region.")
(defun region-unpin ()
"Unpin the current region"
(interactive)
(when pinned-buffer
(let ((window (get-buffer-window pinned-buffer 'visible)))
(setq-local pinned-buffer nil)
(quit-window t window))))
(defun region-pin ()
"Pin the current region to the top."
(interactive)
(when (use-region-p)
(let* ((regionp (buffer-substring (mark) (point)))
(buffer (get-buffer-create "tmp.ml"))
(mode major-mode))
(with-current-buffer buffer
(funcall mode)
(hide-mode-line-mode)
(goto-char (window-end))
(insert regionp)
(goto-char 0))
(setq-local window-min-height 1)
(setq-local pinned-buffer buffer)
(display-buffer-in-direction buffer '((direction . above)
(inhibit-same-window . t)
(window-height . fit-window-to-buffer)))
)))
This allows me to have a temporary window with the major mode of my current one, no modeline, the height of the window fits perfectly the selection and the cursor is set to the beginning of it to have the full text displayed in it but whenever I want to pin some more text it goes to the end of the buffer, insert the selected region and goes back up.

Programmatically detect if any line in a buffer is wrapping?

I have an idea for a possibly cool/probably stupid emacs script which would dynamically resize text to fill available space.
One thing I can't seem to figure out is how to query the current buffer to see if any lines are currently being wrapped. How would I do it?
You can check if any lines are wrapped in the current buffer with function like this:
(defun wrapped-lines-p ()
(save-excursion
(goto-char (point-min))
(let ((long-line-regexp
(format "^.\\{%d\\}.+$" (window-body-width))))
(search-forward-regexp long-line-regexp nil t))))
As noted in the comments, this doesn't take into account the buffer's font size. Since buffers can have a mix of different sized fonts, the window text pixel size needs to be tested. Try this:
(defun wrapped-lines-p ()
(let ((window-width-pixels (window-body-width nil t)))
(> (car (window-text-pixel-size nil nil nil (1+ window-width-pixels)))
window-width-pixels)))
Note that "any lines are currently being wrapped" is a property of the window, not the buffer.
Given a window, you can scan it from top visible line to bottom and compare line length to window-width:
(defun window-long-lines-p ()
"Return t is any visible line in the current window is longer than window width."
(save-excursion
(move-to-window-line -1)
(let ((end (point)) here
found-long-line)
(move-to-window-line 0)
(while (and (not found-long-line)
(< (setq here (point)) end))
(when (< (window-width)
(- (progn (forward-line 1)
(point))
here))
(setq found-long-line t)
(message "long line: %d" (1- (line-number-at-pos)))))
found-long-line)))

Make frames in Emacs GUI behaves like frames in Terminal

In terminal, Emacs manage multiple frames with names like F1, F2.... because it can't create multiple OS windows. I want the GUI version to behave this way, that is, instead of creating multiple OS windows, I want it to create many virtual frames inside a single Emacs window. Is there a way?
There is a way to mimic the frame switching behavior of terminal emacs in GUI. Here is what I did. Basicly it uses make-frame-invisible to hide inactive frames. It works well on archlinux with i3.
(defsubst +amos--is-frame-daemons-frame (f)
(and (daemonp) (eq f terminal-frame)))
(defun +amos--frame-list-without-daemon ()
"Return a list of frames without the daemon's frame."
(if (daemonp)
(filtered-frame-list
#'(lambda (f) (not (+amos--is-frame-daemons-frame f))))
(frame-list)))
(defun +amos/workspace-new ()
(interactive)
(let ((name (frame-parameter nil 'name))
(oframe (selected-frame)))
(select-frame (if (s-starts-with? "F" name)
(make-frame)
(make-frame `((name . ,name)))))
(make-frame-invisible oframe t))
(setq +amos--frame-list (reverse (+amos--frame-list-without-daemon))))
(setq +amos-tmux-need-switch nil)
;; TODO ring lru
(defun +amos/workspace-delete ()
(interactive)
(let ((f (selected-frame)))
(select-frame (previous-frame))
(make-frame-visible)
(delete-frame f))
(setq +amos--frame-list (reverse (+amos--frame-list-without-daemon)))
(+doom-modeline|set-selected-window)
(realign-windows)
(when +amos-tmux-need-switch
(shell-command! "tmux switch-client -t amos\; run-shell -t amos '/home/amos/scripts/setcursor.sh $(tmux display -p \"#{pane_tty}\")'")
(setq +amos-tmux-need-switch nil)))
(defun +amos/workspace-switch-to (index)
(interactive)
(when (< index (length +amos--frame-list))
(let ((frame (nth index +amos--frame-list))
(oframe (selected-frame)))
(select-frame frame)
(raise-frame frame)
(make-frame-invisible oframe t)
(setq +amos-tmux-need-switch nil)
(realign-windows)
(recenter))))
(defun +amos/workspace-switch-to-1 () (interactive) (+amos/workspace-switch-to 0))
(defun +amos/workspace-switch-to-2 () (interactive) (+amos/workspace-switch-to 1))
(defun +amos/workspace-switch-to-3 () (interactive) (+amos/workspace-switch-to 2))
(defun +amos/workspace-switch-to-4 () (interactive) (+amos/workspace-switch-to 3))
(defun +amos/workspace-switch-to-5 () (interactive) (+amos/workspace-switch-to 4))
(defun +amos/workspace-switch-to-6 () (interactive) (+amos/workspace-switch-to 5))
(defun +amos/workspace-switch-to-7 () (interactive) (+amos/workspace-switch-to 6))
(defun +amos/workspace-switch-to-8 () (interactive) (+amos/workspace-switch-to 7))
(defun +amos/workspace-switch-to-9 () (interactive) (+amos/workspace-switch-to 8))
(defun +amos-workspace-cycle (off)
(let* ((n (length +amos--frame-list))
(index (-elem-index (selected-frame) +amos--frame-list))
(i (% (+ off index n) n)))
(+amos/workspace-switch-to i)))
(defun +amos/workspace-switch-left () (interactive) (+amos-workspace-cycle -1))
(defun +amos/workspace-switch-right () (interactive) (+amos-workspace-cycle +1))
(defun +amos|maybe-delete-frame-buffer (frame)
(let ((windows (window-list frame)))
(dolist (window windows)
(let ((buffer (window-buffer (car windows))))
(when (eq 1 (length (get-buffer-window-list buffer nil t)))
(kill-buffer buffer))))))
(add-to-list 'delete-frame-functions #'+amos|maybe-delete-frame-buffer)
If what you mean is that you want to be able to access frames by name, then yes, you can do this with Icicles.
By default, C-x 5 o is bound to multi-command icicle-select-frame. This lets you select one or more frames by name.
A frame's name comes from its name frame parameter. It is suffixed as needed by [NUMBER], to
make it unique. For example, in a context where frames are named for
their buffers and you have two frames showing buffer *Help*, one of
the frames will be called *Help*[2] for use with this command.
Frame selection with C-x 5 o uses completion and cycling. The completion can be vanilla Emacs completion or regexp (including, of course, substring) completion. (It can also be any of several fuzzy completions.)
(If, for some reason, you want the frame names to be just F1, F2, etc., as with terminal Emacs, then you just need to do that at the level of frame parameter name. You can do that using hooks etc.)

Margin width toggle in Emacs?

I'm using the following snippet to set a 1-character margin to both edges of an Emacs buffer (thanks to zev!):
(add-hook 'window-configuration-change-hook
(lambda ()
(set-window-margins (car (get-buffer-window-list (current-buffer) nil t)) 1 1)))
However, I'd also like to have another configuration that gives me very wide left and right margins (say, 30 chars). How could I assign a hotkey for toggling between these two states, possibly over all buffers?
I tried the following:
(global-set-key [(control f10)]
(add-hook 'window-configuration-change-hook
(lambda ()
(set-window-margins (car (get-buffer-window-list (current-buffer) nil t)) 30 30))))
But got this error:
Wrong type argument: commandp, ((lambda nil (set-window-margins (car (get-buffer-window-list (current-buffer) nil t)) 30 30)) (lambda nil (set-window-margins (car (get-buffer-window-list (current-buffer) nil t)) 1 1)) frame-local-variables-check)
Where am I wrong? Thanks for any explanations. Cheers!
EDIT:
I am well aware of darkroom-mode that provides such wide margins. But it doesn't seem to work flawlessly on my XP.
This should get you going:
(global-set-key [C-f10]
(lambda ()
(interactive)
(set-window-margins (car (get-buffer-window-list (current-buffer) nil t)) 30 30)))
Your example does not work as the global-set-key is expecting the second argument to be a command, hence the error. Also your key definiton is a little odd.
For your comment:
(walk-windows (lambda (window) (set-window-margins window 30 30)) nil t)