10

Is there a way to get a split screen in the notebook interface?

I want a movable horizontal bar in a window with two views into the same notebook, each of which is independently scrollable.

An alternative, though less useful, would be to have two windows open on the same notebook. Both would have to be "live" of course, each immediately reflecting any notebook change made to the other.

LCarvalho
  • 9,233
  • 4
  • 40
  • 96
G. Shults
  • 413
  • 2
  • 10
  • 1
    On the Related sidebar, I found this link. – march Oct 16 '15 at 17:01
  • @march's link to "Open two copies of the same notebook" is a useful stopgap, but still far less than ideal. The copy is read-only, so all editing is still in the master, which means jumping back and forth between edit locations in the master (This wouldn't be quite so bad if the notebook editor provided bookmarking capability to facilitate that). The copy is also static, though it can be manually synced to the master as needed. – G. Shults Oct 17 '15 at 15:00

2 Answers2

3

No, this is not possible.

The most common use case for the horizontal splitter is working with a long document where it is useful to see two far-apart sections at the same time. One way to work with such documents in Mathematica is to structure them into sections. Sections can be collapsed and expanded by double clicking their cell bracket (or alternatively by enabling those little triangular openers that the documentation pages have). Open only sections you need at a time.


Note: Of course proving that something is not possible is hard, so I might be proven wrong. In this case I am pretty sure though that there is no way.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
  • 1
    The expanding/collapsing sections is what I have been using. While it is better than nothing, it's just barely so. I find it tedious and clumsy. During software development there is never a simple linear decomposition which easily helps you jump around among code and data. Too many different pieces interact in too many different ways. – G. Shults Oct 17 '15 at 21:09
  • what gives you very high but not absolute confidence that it's not possible? – G. Shults Oct 17 '15 at 21:18
  • @G.Shults I'd say notebooks are an ideal interface for interactive work, but not for package development. I guess that's what you mean when you say "software development". Consider using the Wolfram Workbench or the IDEA plugin for writing packages. Both have split views. – Szabolcs Oct 17 '15 at 21:19
  • 1
    @G.Shults Many years of experience using Mathematica and some understanding of what can be done with notebooks. I'm never going to say, "no, this is absolutely not possible" as I cannot prove that. But I thought I'd spare you the trouble to keep looking, since I believe it exceedingly unlikely to have some hidden feature that will enable this for notebooks. – Szabolcs Oct 17 '15 at 21:19
  • Retracted comment that was submitted prematurely – G. Shults Oct 19 '15 at 18:53
  • @Szabolics Workbench is expensive for a retiree on a budget. Even then, package development is also an interactive process. Maybe I didn't understand the best way to use it, but I tried Workbench several years ago when I had a Premier subscription. Going back and forth between the source, the interactive testing, and making sure new definitions were loaded in the environment (and that old ones were removed from the environment) were are also problematic. Workbench also lacks (or did then) many of the editing assists of notebooks. A split screen notebook would still be a great feature. – G. Shults Oct 19 '15 at 20:58
  • @G.Shults Oh, I certainly agree that it would be a great feature :-) I just don't think it's possible at this moment. I do not use Workbench anymore, but for writing packages I always use the IDEA plugin now. And I do use both code folding and split view with it. It only works as a (very advanced) editor, with many code assist features. At the same time I keep Mathematica open, and use it to load and test the package, and also as a starting point for implementing new functions that eventually get copied into the package. – Szabolcs Oct 19 '15 at 21:04
  • This plugin, as well as the community edition of IDEA, are free. – Szabolcs Oct 19 '15 at 21:05
  • @Szabolics The IDEA plugin looked interesting until I found that it neither supports the current nor previous release of Mac OS X – G. Shults Oct 21 '15 at 19:12
  • @G.Shults That's not the case. I've been using it on every current OS X release since it came out. I'm using it on El Capitan now. You might need to install Java: https://support.apple.com/kb/DL1572?locale=en_GB It's the same thing with the Workbench. – Szabolcs Oct 21 '15 at 20:13
  • Do you know why their website download page explicitly says only through 10.9 then? It can't be the Java download issue because that requirement would pre-date 10.9. I get nervous trying something that the developer says doesn't work. – G. Shults Oct 21 '15 at 20:19
  • @G.Shults I don't know. Most likely just an oversight. – Szabolcs Oct 21 '15 at 20:32
2

I tried to do this, and ended up with some of the functionality you want. The idea I pursued was to create a copy (from a non-maximized window because I don't know how to programmatically resize a maximized one):

copy[original_] := Module[{copy = NotebookPut[NotebookGet[original]],
                           size = WindowSize /. AbsoluteOptions[original, WindowSize],
                           margins = WindowMargins /. AbsoluteOptions[original, WindowMargins]},
                    size[[2]] /= 2;
                    SetOptions[original, WindowSize -> size, WindowMargins -> margins];
                    margins[[2, 2]] += size[[2]] + 50; (* fails for maximized window *)
                    SetOptions[copy, WindowSize -> size, WindowMargins -> margins];
                    copy]

Then refresh the copy whenever changes to the original are made, and vice versa:

refresh[original_, copy_, options_] := (NotebookPut[NotebookGet[original], copy]; 
                                        SetOptions[copy, options];)

Initially, I tried to do the latter by just using NotebookEventActions to handle KeyDown events:

sync[original_, copy_] := SetOptions[original, NotebookEventActions ->
    {"KeyDown" :> refresh[original, copy, AbsoluteOptions[copy]], PassEventsDown -> True}]

This function is called twice, once with 1st and 2nd arguments being the original and copy notebook respectively, and once with the arguments swapped, e.g.:

split[original_] := Module[{copy = copy[original]}, 
                           {sync[original, copy], sync[copy, original]}]

Apart from being prohibitively slow, this method has several limitations:

  1. Synchronization only occurs when typing in a notebook, and is not triggered by other ways notebooks can be changed.
  2. Keyboard shortcuts, e.g. Shift+Enter or Ctrl+V, aren't handled.
  3. Refreshed scroll position is always reset to the end of the notebook.
  4. Synchronization occurs before key is input.

Limitation 1 may be overcome by calling refresh on other user inputs in NotebookEventActions, and in CellEpilog. For limitation 2, I'm not sure - maybe messing with the keyboard translations file would work. In any case, pressing an additional arrow key after using the shortcut would force synchronization.

Limitation 3 can be somewhat alleviated by using SelectionMove in refresh to control the scroll position, which cannot otherwise be controlled programmatically:

refresh[original_, copy_, options_] := 
       (If[0 == Length[SelectedCells[copy]], SelectionMove[copy, Next, Cell];
        If[0 == Length[SelectedCells[copy]], SelectionMove[copy, Previous, Cell];]];
        Module[{position = FirstPosition[Cells[copy], SelectedCells[copy][[1]]][[1]]}, 
        NotebookPut[NotebookGet[original], copy];
        SetOptions[copy, options];
        SelectionMove[Cells[copy][[position]], All, Cell];])

This function can be refined to have finer control over the scroll position if need be.

As with limitation 2, an easy fix for limitation 4 is to press an arrow key to force synchronization whenever necessary. Otherwise, because KeyDown events reach the notebook event handlers before input, we need to somehow delay handling the events until after input. The only way I could do so was to queue the event handling actions, instead of letting them run preemptively. Because there isn't a direct way to do this via NotebookEventActions, I had to resort to Dynamic without SynchronousUpdating:

sync[original_, copy_] := DynamicModule[{key, options = AbsoluteOptions[copy]}, Dynamic[{
     SetOptions[original, NotebookEventActions -> 
    {"KeyDown" :> (key = CurrentValue["EventKey"]), PassEventsDown -> True}];
     Dynamic[options = AbsoluteOptions[copy];],
     Dynamic[refresh[original, copy, options];
     key, TrackedSymbols :> {key}, SynchronousUpdating -> False]}]]

Similar to the original non-dynamic sync, this function is called twice. However, because it creates dynamic output, which must be maintained for dynamic synchronization, the function must be called in a separate notebook, lest the dynamic in one notebook refresh the dynamic in the other in an infinite loop. CreateDialog[split[EvaluationNotebook[]]] works but prints a "NotebookPredictions" packet error I don't understand. If desired, the error can be avoided by calling split from another notebook.

Speed is still an issue with this method. Also, beware of race conditions resulting from switching off SynchronousUpdating. E.g. any input entered in a notebook before a queued refresh will be cleared. If immediate synchronization isn't required, sync can be modified to only synchronize when switching input notebooks, and queueing refresh is no longer needed:

sync[original_, copy_] := DynamicModule[{options = AbsoluteOptions[copy]}, Dynamic[{
     Dynamic[options = AbsoluteOptions[copy];], 
     Dynamic[If[InputNotebook[][[2]] == copy[[2]], refresh[original, copy, options]], 
     TrackedSymbols :> {InputNotebook}]}]]

Alternatively, replace "KeyDown" in the first sync definition above to synchronize only on "MouseClicked", "LeftArrowKeyDown" etc.

obsolesced
  • 501
  • 4
  • 12