7

One can of course Ctrl + MouseClick to select 1st and 3rd cell.

But how to select non adjacent Cells programmatically?

I was looking for neat examples here or in the documentation but I was not able to find suitable Option / function / FrontEndToken.


The method I've prepared is good enough for many cases so I'm posting it as an answer but I will gladly accept other if it deals with following issues:

  • Using my method, to expand selection one need to rescan all already selected. Not really efficient.

and a minor issues

  • This is ugly big amout of code for such simple operation.
  • I was not able to manipulate CellTags with CurrentValue. Not quite sure why.
Kuba
  • 136,707
  • 13
  • 279
  • 740

2 Answers2

8

So the only way that came to my mind and worked was to use CellTags and NotebookLocate.

SetAttributes[selectCells, HoldRest];

selectCells[cells_, nb_: InputNotebook[]] := With[{
  tag = ToString @ Unique["Tag"]
  },
  (SelectionMove[#, All, Cell, AutoScroll -> False]; 
   FrontEndExecute@FrontEnd`SelectionAddCellTags[nb, tag]
  ) & /@ cells;
  NotebookLocate[tag];
  FrontEndExecute@FrontEnd`SelectionRemoveCellTags[nb, tag]   
];

This only works with current InputNotebook[] so you can use it in the notebook which is a parent to cells you want to select or you can use it from palette.


For testing purposes evaluate:

selectCells @ Cells[][[{1, 3}]]

or

 selectCells @ Cells[][[;; ;; 2]]

and the result should be something like:

enter image description here

Kuba
  • 136,707
  • 13
  • 279
  • 740
  • Personally I prefer a NotebookFind to a NotebookLocate because I can more easily specify which notebook to so the selection in. – b3m2a1 Aug 23 '17 at 21:02
  • @b3m2a1 It think your preference is indeed better. I must have missed that commands like NotebookFind[nb, "tag", All, CellTags] work. Quite often my self answers are imperfect as I need stuff to be done and proceed with other duties. Will update this answer soon. – Kuba Aug 25 '17 at 22:13
4

Instead of selecting cells and then using the FrontEnd`SelectionAddCellTags / FrontEnd`SelectionRemoveCellTags functions, you can just use SetOptions on the CellObject (i.e., no need to select them first). Here is a version of selectCells that does this:

selectCells[cells_List]:=With[
    {
    tmp = CreateUUID[],
    parents = DeleteDuplicates @ Map[ParentNotebook] @ cells
    },
    Internal`WithLocalSettings[
        addCellTag[cells, tmp],
        NotebookFind[#1, tmp, All, CellTags]& /@ parents,
        removeCellTag[cells, tmp]
    ]
]

addCellTag[cells_, tag:_String|{__String}] := With[
    {
    new = Flatten /@ Thread[{CurrentValue[cells, CellTags], Sequence@@tag}]
    },
    MapThread[SetOptions[#1, CellTags->#2]&, {cells, new}]
]

removeCellTag[cells_, tag:_String|{__String}] := With[
    {
    new = DeleteCases[
        Flatten @* List /@ CurrentValue[cells, CellTags],
        Alternatives @@ tag,
        {2}
    ]
    },
    MapThread[SetOptions[#1, CellTags->#2]&, {cells, new}];
]

It uses some of the ideas behind @b3m2a1's answer to essentially the same question, e.g., CreateUUID (to create a unique tag), NotebookFind (so that it works with cells from any notebook) and ParentNotebook (to figure out what notebooks contain the cells).

Carl Woll
  • 130,679
  • 6
  • 243
  • 355