nteract / nteract

📘 The interactive computing suite for you! ✨

Home Page:https://nteract.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RFC: cell merge and split (redux mechanics only)

john-swanke opened this issue · comments

Objective

We are allowing users to perform these two cell operations:

  • Merge two adjacent cells into one cell.
  • Split a single cell into two distinct adjacent cells.

This RFC concerns cell merge/split redux mechanics (i.e., actions, reducers, etc.). There will be a separate RFC and PR for updating UI and keyboard shortcuts to expose the features. We're hoping to contribute the groundwork quickly as discussion on how to present features will likely take more time.

Motivation

Cell merge/split will reduce user operations to perform equivalent tasks. One cell merge, for example, removes the need to focus a cell, copy-and-paste text, then delete the source cell. Also, both cell merge and split are offered in competing apps like JupyterLab and VSCode Insiders.

Design Proposal

Code changes

Implementation for cell merge/split is similar to other cell operations, such as create, move and copy. New reducers will thus be be placed in packages/reducers/src/core/entities/contents/notebook.ts. New document action types MergeCell and SplitCell will also be created.

The MergeCell action type will at least have parameters id and destinationId--the cell ids of the merging cells--as well as a boolean above. Parameter id will be the cell id of the cell invoking the merge action; parameter destinationId is the cell id of the cell to which invoking cell will merge; and above describes whether the merge is with previous/next cell.

The SplitCell action type will at least have parameter id, which is the cell id of the desired cell to split.

Reducers for both actions logically build upon already existing reducers. At the most basic level, cell merge will use DeleteCell reducer (a cell needs to be removed for two cells consolidate to one) and cell split will use CreateCell[Above/Below] reducer (a cell needs to be created to contain part of original source). Each reducer's NotebookModel state parameter will be used for both to obtain current cell types, cell order indices, etc. which assists cell merge/split's implementation.

Expected behavior

A basic description of cell merge and split is described in the Objective section above, but below are more specific guidelines on how these two features should act.

Cell merge

  • The merging cells aren't required to be of the same type. If they're different, the merged cell will have cell type of upper-most cell.
  • If either of the cells have output, only retain the output of the upper-most cell after merge.
  • The mode of the cell which invokes the merge will be retained in the resulting merged cell. For example, if the user is in edit mode for cell and merges with an adjacent cell, then the merged cell will also be in edit mode and have the caret in the same place.
  • The resulting merged cell will have a new distinct cell id that is different from the two source cells.
  • Metadata for the newly merged cell will be largely that of the upper-most source cell. There are likely exceptions where the lower cell's metadata will need to be retained, such as image specification.

Cell split

  • Both code and markdown cells can be split. The resulting two cells are of the same cell type as the source cell.
  • The position of the caret in the cell determines where the split will happen. The cell will split below the caret and the resulting upper cell will remain focused with the caret in the same location.
  • The resulting two cells will have new distinct ids that are different from the original cell.
  • If the invoking cell has output, then output is retained in the resulting upper cell.

Jest tests will be made to ensure all expected behavior is followed.

Performance Implications

I do not expect any significant performance impact. The other simpler reducers set the lower bound on performance as they're used to implement merge/split; for example, it's reasonable to assume cell merge has largely the same cost as two cell deletions and one cell insertion. Nonetheless, Jest testing can track performance as well.

Dependencies

Aforementioned reducers and selectors are dependencies as they're used in the cell merge/split reducing functions. Because these dependencies are noncomplex any changes to dependency wouldn't be difficult to handle/update.

Engineering Impact

  • There is likely a negligible impact to artifact size and build/test times as features are limited in scope.
  • Cell merge and split can be added to core SDKs which will be eventually used when exposing feature.
  • @tannar-msft and I (@john-swanke) can take ownership of maintaining this feature in nteract; we'll do our best to stay updated on user and developer feedback.

User Impact

To be discussed in forthcoming View-layer RFC. These redux changes should not impact users in any way prior to feature exposure/enabling.

Questions and Discussion Topics

With regard to the expected behavior section above, the specification was decided after reviewing cell merge and split in Google Colab, JupyterLab and VSCode Insiders. Please feel free to bring up other behavior that is important to define!

Also, I'm pretty new to the nteract project, so if this RFC doesn't meet contributor standards and/or my proposed implementation could be improved please let me know!

Metadata for the newly merged cell will be largely that of the upper-most source cell. There are likely exceptions where the lower cell's metadata will need to be retained, such as image specification.

I'm assuming this matches what JupyterLab is doing? Maybe this is something worth documenting in nbformat (maybe jupyter_core as well) to guide implementations?

Continuing on with my thinking for metadata since it's more hidden data to the user, I wonder how we should treat it on split cells. I suppose it's the same as a copy-cell, though maybe the best thing again is to just have a consistent UX across Jupyterlab and classic.

Thanks @rgbkrk! I can bring this up in both nbformat and jupyter_core.

And good follow-up on split... I suppose we could either duplicate original cell's metadata into the newly split cells or follow the trend of upper cell preference and keep metadata only for the upper-most split cell. Regardless, I'll investigate JupyterLab and Jupyter Notebook classic to analyze their behavior to follow what they do.