alphapapa / burly.el

Save and restore frames and windows with their buffers in Emacs
GNU General Public License v3.0
301 stars 14 forks source link

Workspace ideas #37

Open hrehfeld opened 3 years ago

hrehfeld commented 3 years ago

e.g: I restored burly bookmark foo, then restore burly bookmark bar. Please give me the option to automatically save the old bookmark foo before switching to the new one.

Ideally, C-u burly-open-bookmark toggles the behavior, and which behavior is the default is configurable.

alphapapa commented 3 years ago

This would be a very confusing behavior. It would surely lead to users accidentally overwriting their carefully crafted bookmarks.

What you seem to be wanting is a kind of workspace-switching behavior. Burly could be used as a backend for something like that, and I have some ideas along those lines (e.g. see the recently added burly-open-last-bookmark command). But they need to be designed thoughtfully. It would also probably make sense to try to integrate it with Emacs 27 tab bars (I'm imagining something like, middle-clicking on a tab would restore to a Burly-bookmarked default state, while left-clicking it would simply change back to that tab's current state).

Anyway, if you really want the behavior you described, you could easily use the variable burly-opened-bookmark-name to implement it yourself in a simple command.

hrehfeld commented 3 years ago

Thanks for your input!

I'm working on a small package burly-workspaces, will post here in a few days when I find that it works well enough. I would definitely appreciate your feedback!

alphapapa commented 3 years ago

Hm, I'm thinking about adding a burly-workspace-mode feature to this package in the future to integrate it with Emacs 27 tab-bars. It'd be good to avoid namespace conflicts and confusion. Maybe, rather than publishing a new package, we could work together to integrate these ideas into this package.

Basically, my idea is that opening a Burly bookmark would open a new tab with the restored window configuration. Then, switching between window configurations with the tab bar would work normally. But an additional command would restore the current tab's Burly bookmark, like "resetting" the workspace to the bookmarked configuration. I'm thinking, e.g. to bind that to middle-click on the tabs, so left-clicking would select the tab normally, and middle-clicking it would reset it (I don't recall if middle-click is already bound to something on the tabs). Of course, users could also bind it to a key of their choice.

What do you think? Thanks.

gagbo commented 3 years ago

It looks interesting, I do wonder how it would interact with bufler though.

Currently I'm using Bufler to store arbitrary workspace information directly in buffer-local variables, and I was starting to wonder how I could use that to get "per-workspace" management (i.e. have a "open workspace" command, that prompts for a list of known workspaces, and then restore them to the last saved state).

For the time being I'm struggling to find time to finish properly https://github.com/alphapapa/bufler.el/pull/53 but the end-goal was kind of the same : I'd like to have a workspace feature integrated with tab-bar where I can save/kill/restore collections of buffer+window configurations. So marking them, saving configurations and restoring configurations are really the 3 steps I'd want.

I didn't think through using bufler to manage workspaces completely, but I'm interested in seeing where this goes.

To go back on topic,

Basically, my idea is that opening a Burly bookmark would open a new tab with the restored window configuration.

That would work nicely, and it'd be nice if there was a switch to automatically save/overwrite the bookmark on window configuration change as well, instead of keeping the bookmark for the middle click feature. That would allow a seamless "get back to workspace" experience imo.

alphapapa commented 3 years ago

Some of these ideas are exploratory, so I don't know where they will end up. It seems to me that the ideas described here are not directly related to Bufler. To me, Bufler is about automatically grouping buffers for easier manipulation, while Burly is about restoring window and frame configurations and their buffers.

Bufler's "workspace mode" is really internal to Bufler; it only affects which buffers certain Bufler commands offer for completion.

Bufler's "tabs mode" does integrate with the Emacs 27+ tab bar, and I don't know if it would be feasible to have both bufler-tabs-mode and a hypothetical burly-workspace-mode or burly-tabs-mode active at the same time. It might be, because the tab bar code is quite extensible; assuming it's possible, the existing bufler-tabs-mode code would need to be changed to extend rather than override the relevant tab-bar functions. (Or it might be necessary to enhance the tab-bar library to allow for more extensibility and co-existence of such extensions. I don't think it was quite designed with that in mind.)

I'd like to have a workspace feature integrated with tab-bar where I can save/kill/restore collections of buffer+window configurations. So marking them, saving configurations and restoring configurations are really the 3 steps I'd want.

Yes, that's basically my idea, and I think it belongs in Burly rather than Bufler.

That would work nicely, and it'd be nice if there was a switch to automatically save/overwrite the bookmark on window configuration change as well, instead of keeping the bookmark for the middle click feature. That would allow a seamless "get back to workspace" experience imo.

We could certainly offer an option for that. I guess that might require each Burly bookmark to have multiple states, like a "reset-to" state and a "last-used" state, because the appeal of Burly, to me, is being able to restore a certain state, not whatever arrangement of buffers I happened to have opened last--that arrangement is constantly changing as I use different commands, view different files and buffers, etc.

hrehfeld commented 3 years ago

Hm, I'm thinking about adding a burly-workspace-mode feature to this package in the future to integrate it with Emacs 27 tab-bars. It'd be good to avoid namespace conflicts and confusion. Maybe, rather than publishing a new package, we could work together to integrate these ideas into this package.

Yep, I was about to ask that. :+1:

Basically, my idea is that opening a Burly bookmark would open a new tab with the restored window configuration. Then, switching between window configurations with the tab bar would work normally. But an additional command would restore the current tab's Burly bookmark, like "resetting" the workspace to the bookmarked configuration. I'm thinking, e.g. to bind that to middle-click on the tabs, so left-clicking would select the tab normally, and middle-clicking it would reset it (I don't recall if middle-click is already bound to something on the tabs). Of course, users could also bind it to a key of their choice.

Hmm, I don't use tabs at all, so it's hard for me to talk about good workflows there. It sounds like a variable like burly-restore-function might come in handy to distinguish between "open a new tab and apply workspace" and just "apply workspace"?

I'm currently just trying to model eyebrowse behavior, which is basically as I described:

  1. if you save a workspace (= window bookmark currently), burly-opened-bookmark-name is updated accordingly.
  2. if you previously restored another workspace and the newly saved workspace name did exist before, I save the previously opened one (unless prefix arg is given, so you can disable that behavior) before switching. From your description it sounds like this behaves like a bunch of tabs that you can switch between? (Can tab functionality be used instead even when I disable the ui there?). A bunch of frames would behave similar as well.
  3. I also have a helper function to have numeric workspace names.

This is what I have so far, the ability to toggle via minor mode was just added, might still be broken:

  (defvar burly-workspaces-prefix "Workspace " "How to name the
automatic workspaces")
  ;;(setq burly-workspaces-prefix "Workspace ")
  (defun burly-workspaces--bookmark-exists (name) (bookmark-get-bookmark name 'no-error))

  (defun burly-workspaces--burly-open-bookmark-advice (name)
    "Before `burly-open-bookmark' bookmark the previously loaded
bookmark. With a prefix argument `ARG' or if the new `name' is
not a bookmark yet , don't save and just switch."
    (cl-assert name "No name given to advice!")
    (let ((command-is-prefxed current-prefix-arg)
          (current-exists burly-opened-bookmark-name)
          (target-already-opened (string-equal name burly-opened-bookmark-name))
          (target-exists (burly-workspaces--bookmark-exists name)))
      ;;(message "burly-workspaces--burly-open-bookmark-advice %S %S %S %S" name command-is-prefxed target-already-opened target-exists)
      (unless (or command-is-prefxed
                  target-already-opened
                  (not target-exists)
                  (not current-exists))
        (burly-bookmark-windows burly-opened-bookmark-name))
      ))
  (defun burly-workspaces--burly-bookmark-windows-advice (name)
    "After `burly-bookmark-windows', set `burly-opened-bookmark-name' to the name of the newly saved bookmark."
    (let* ((bookmark-name (or name bookmark-current-bookmark))
           (already-open (string-equal bookmark-name burly-opened-bookmark-name)))
      (cl-assert bookmark-name)
      ;;(message "burly-workspaces--burly-bookmark-windows-advice %S %S" bookmark-name already-open)
      (unless already-open
        (setq burly-opened-bookmark-name bookmark-name))))

  (defun burly-workspaces-do (arg num)
    "Switch to workspace bookmark `num' after saving the opened workspace bookmark, or with `prefix-arg' write a new workspace bookmark `num'."
    (interactive "P")
    (let ((name (concat burly-bookmark-prefix burly-workspaces-prefix (number-to-string num))))
      (burly-open-bookmark name)
      ))
  (defun burly-workspaces-do-0 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 0))
  (defun burly-workspaces-do-1 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 1))
  (defun burly-workspaces-do-2 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 2))
  (defun burly-workspaces-do-3 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 3))
  (defun burly-workspaces-do-4 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 4))
  (defun burly-workspaces-do-5 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 5))
  (defun burly-workspaces-do-6 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 6))
  (defun burly-workspaces-do-7 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 7))
  (defun burly-workspaces-do-8 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 8))
  (defun burly-workspaces-do-9 (arg) "See `burly-workspaces-do'." (interactive "P") (burly-workspaces-do arg 9))

  (defvar burly-workspaces-mode-map (make-sparse-keymap) "Keymap
for `burly-workspaces-mode'.")

  (define-minor-mode burly-workspaces-mode
      "FIXME"
    :global t
  :lighter " bws"
  :keymap burly-workspaces-mode-map
  (if burly-workspaces-mode
      (progn
        (advice-add #'burly-bookmark-windows :after #'burly-workspaces--burly-bookmark-windows-advice)
        (advice-add #'burly-open-bookmark :before #'burly-workspaces--burly-open-bookmark-advice))
    (progn
      (advice-remove #'burly-bookmark-windows #'burly-workspaces--burly-bookmark-windows-advice)
      (advice-remove #'burly-open-bookmark #'burly-workspaces--burly-open-bookmark-advice))))

  (defun burly-workspaces-setup-numeric-keys (prefix-keys)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 0")) #'burly-workspaces-do-0)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 1")) #'burly-workspaces-do-1)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 2")) #'burly-workspaces-do-2)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 3")) #'burly-workspaces-do-3)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 4")) #'burly-workspaces-do-4)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 5")) #'burly-workspaces-do-5)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 6")) #'burly-workspaces-do-6)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 7")) #'burly-workspaces-do-7)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 8")) #'burly-workspaces-do-8)
    (define-key burly-workspaces-mode-map (kbd (concat prefix-keys " 9")) #'burly-workspaces-do-9))

;; my config
  (burly-workspaces-setup-numeric-keys "C-. e")

It would probably be much better to just use the numeric argument for numeric switching... hmmm.

hrehfeld commented 3 years ago

Oh, this will get horribly confused when opening multiple frames and restoring workspaces, as burly-opened-bookmark is shared between frames, I guess.

What's the recommended way for frame-local variables?

gagbo commented 3 years ago

What's the recommended way for frame-local variables?

Probably the Frame Parameter API

alphapapa commented 3 years ago

@hrehfeld FYI, I'm not interested in imitating Perspective's design, with numbered workspaces and that sort of thing. If that's what you're looking for, then you'll need to do that part on your own. And, if you're going to do that, then rather than writing your own workspace package, I'd suggest looking into extending Perspective to use Burly as a backend to restore buffers. I designed Burly so that its functions can easily be used in other packages.

What I'm more interested in doing is using Burly to integrate with and extend existing Emacs tools, like how it currently integrates bookmarks with window and frame configurations. Integrating with tab-bar seems like a natural next step.

In any case, I'd ask that you not publish a package named burly-SOMETHING. The design you've shared so far really has nothing to do with Burly, per se; that it uses Burly as a backend to restore buffers is incidental. And using burly as a prefix would imply that it's part of this project, and of course, could cause namespace conflicts.

Having said that, I'm of course happy for you to use Burly in your project, and I'll be glad to help you if I can.

gagbo commented 3 years ago

I'd like to have a workspace feature integrated with tab-bar where I can save/kill/restore collections of buffer+window configurations. So marking them, saving configurations and restoring configurations are really the 3 steps I'd want.

Yes, that's basically my idea, and I think it belongs in Burly rather than Bufler.

I’ve been thinking about it a bit more recently, and I think the main limitation today in burly is that you’d need specific machinery in the frames part of the code.

Glossary

In my current workflow, a workspace is a tab where all buffers share a buffer-local parameter (they belong to the same bufler workspace, and actually when I switch on the tab, the bufler-workspace of the frame changes to match it). A session is set of workspaces, i.e. it is the frame that holds multiple tabs. Maybe it’s not the correct way, but it’s how I will use the words session and workspace

Target flow

When I save a workspace, I need to actually save a single tab, but tabs are frame-parameters. The only way to save a workspace is to save a frameset, with all the other tabs. When I restore a workspace, I need to restore the buffers in the buffer-list that belonged to the workspace, but buffer-list is a frame-parameter. So if I want to restore the buffers in a new tab, I need some logic to fuse the current buffer-list with the one I want to restore.

It’s more complex than what I’d like, so for the time being I think I’ll only focus on saving/restoring sessions in my endeavours.

My ramblings about what might be left

Trying to save/restore "workspaces as individual tabs" might have some tricks hidden.

For saving, I think having some way to specify a predicate function (that takes a buffer as parameter) to burly-frames-url would be nice, so that one can choose to keep a subset of parameters on save. Maybe that’s already what burly-window-persistent-parameters and burly-frameset-filter-alist do, my next exploration step is to go through these parameters and see what I can do

Restoring, which in my mind means "fuse 2 sets of frame-parameters in 1 frame", is not clear enough in my head to have an opinion other than "it’s going to be complex".