10
$\begingroup$

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.

$\endgroup$
2
  • 1
    $\begingroup$ On the Related sidebar, I found this link. $\endgroup$
    – march
    Commented Oct 16, 2015 at 17:01
  • $\begingroup$ @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. $\endgroup$
    – G. Shults
    Commented Oct 17, 2015 at 15:00

3 Answers 3

3
$\begingroup$

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.

$\endgroup$
12
  • 1
    $\begingroup$ 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. $\endgroup$
    – G. Shults
    Commented Oct 17, 2015 at 21:09
  • $\begingroup$ what gives you very high but not absolute confidence that it's not possible? $\endgroup$
    – G. Shults
    Commented Oct 17, 2015 at 21:18
  • $\begingroup$ @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. $\endgroup$
    – Szabolcs
    Commented Oct 17, 2015 at 21:19
  • 1
    $\begingroup$ @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. $\endgroup$
    – Szabolcs
    Commented Oct 17, 2015 at 21:19
  • $\begingroup$ Retracted comment that was submitted prematurely $\endgroup$
    – G. Shults
    Commented Oct 19, 2015 at 18:53
2
$\begingroup$

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.

$\endgroup$
1
  • $\begingroup$ I did not notice this post originally. I only discovered it after @Alex's answer of Nov 19, 2024. This is an interesting approach, but I would not use it due to its complexity, admitted slowness and the existence of race conditions. $\endgroup$
    – G. Shults
    Commented Nov 23 at 16:11
1
$\begingroup$

Not quite an answer, but an alternative to a split screen mode:

I stumble on this issue quite frequently as I often write rather long notebooks. Using sections is very useful but they are better suited for sectioning conceptual parts of the code rather than functional ones.

My latest resort was to make use of mathematica packages. The way I use them is to have a separate notebook that operate on a different Kernel to avoid context issues when loading the package. This is where I write all my generalised functions and the notebook is there to import the package and apply the desired functions.

What is also cool about them is that MMA packages are GitHub friendly, unlike a standard notebook.

I have created a mini list of useful links and pages that help people put things together when learning about packages. It is a slightly painful process, but may be worth looking into it if you often write long notebooks.

Until they allow for a split-screen capability, I find this to be a good step forward.

Best of luck.

A.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.