# Split screen view in notebook interface?

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.

• 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

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.

• 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
• @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

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.