public static void addBulkSelectionListener(@NotNull Editor editor, @NotNull SelectionListener listener, @NotNull Disposable disposable) {
    Ref<Pair<int[], int[]>> selectionBeforeBulkChange = new Ref<>();
    Ref<Boolean> selectionChangedDuringBulkChange = new Ref<>();
    editor.getSelectionModel().addSelectionListener(new SelectionListener() {
      @Override
      public void selectionChanged(@NotNull SelectionEvent e) {
        if (selectionBeforeBulkChange.isNull()) {
          listener.selectionChanged(e);
        }
        else {
          selectionChangedDuringBulkChange.set(Boolean.TRUE);
        }
      }
    }, disposable);
    editor.getCaretModel().addCaretActionListener(new CaretActionListener() {
      @Override
      public void beforeAllCaretsAction() {
        selectionBeforeBulkChange.set(getSelectionOffsets());
        selectionChangedDuringBulkChange.set(null);
      }

      @Override
      public void afterAllCaretsAction() {
        if (!selectionChangedDuringBulkChange.isNull()) {
          Pair<int[], int[]> beforeBulk = selectionBeforeBulkChange.get();
          Pair<int[], int[]> afterBulk = getSelectionOffsets();
          listener.selectionChanged(new SelectionEvent(editor, beforeBulk.first, beforeBulk.second, afterBulk.first, afterBulk.second));
        }
        selectionBeforeBulkChange.set(null);
      }

      private Pair<int[], int[]> getSelectionOffsets() {
        return Pair.create(editor.getSelectionModel().getBlockSelectionStarts(), editor.getSelectionModel().getBlockSelectionEnds());
      }
    }, disposable);
  }
public static void addBulkSelectionListener(@NotNull Editor editor, @NotNull SelectionListener listener, @NotNull Disposable disposable)