@Nullable
  public Runnable startNonCustomTemplates(
      final Map<TemplateImpl, String> template2argument,
      final Editor editor,
      @Nullable final PairProcessor<String, String> processor) {
    final int caretOffset = editor.getCaretModel().getOffset();
    final Document document = editor.getDocument();
    final CharSequence text = document.getCharsSequence();

    if (template2argument == null || template2argument.isEmpty()) {
      return null;
    }
    if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), myProject)) {
      return null;
    }

    return () -> {
      if (template2argument.size() == 1) {
        TemplateImpl template = template2argument.keySet().iterator().next();
        String argument = template2argument.get(template);
        int templateStart = getTemplateStart(template, argument, caretOffset, text);
        startTemplateWithPrefix(editor, template, templateStart, processor, argument);
      } else {
        ListTemplatesHandler.showTemplatesLookup(myProject, editor, template2argument);
      }
    };
  }
  @Nullable
  public Runnable prepareTemplate(
      final Editor editor,
      char shortcutChar,
      @Nullable final PairProcessor<String, String> processor) {
    if (editor.getSelectionModel().hasSelection()) {
      return null;
    }

    PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, myProject);
    if (file == null) return null;

    Map<TemplateImpl, String> template2argument =
        findMatchingTemplates(file, editor, shortcutChar, TemplateSettings.getInstance());

    List<CustomLiveTemplate> customCandidates =
        ContainerUtil.findAll(
            CustomLiveTemplate.EP_NAME.getExtensions(),
            customLiveTemplate ->
                shortcutChar == customLiveTemplate.getShortcut()
                    && (editor.getCaretModel().getCaretCount() <= 1
                        || supportsMultiCaretMode(customLiveTemplate)));
    if (!customCandidates.isEmpty()) {
      int caretOffset = editor.getCaretModel().getOffset();
      PsiFile fileCopy = insertDummyIdentifierIfNeeded(file, caretOffset, caretOffset, "");
      Document document = editor.getDocument();

      for (final CustomLiveTemplate customLiveTemplate : customCandidates) {
        if (isApplicable(customLiveTemplate, editor, fileCopy)) {
          final String key =
              customLiveTemplate.computeTemplateKey(new CustomTemplateCallback(editor, fileCopy));
          if (key != null) {
            int offsetBeforeKey = caretOffset - key.length();
            CharSequence text = document.getImmutableCharSequence();
            if (template2argument == null
                || !containsTemplateStartingBefore(
                    template2argument, offsetBeforeKey, caretOffset, text)) {
              return () -> customLiveTemplate.expand(key, new CustomTemplateCallback(editor, file));
            }
          }
        }
      }
    }

    return startNonCustomTemplates(template2argument, editor, processor);
  }
  public Map<TemplateImpl, String> findMatchingTemplates(
      final PsiFile file,
      Editor editor,
      @Nullable Character shortcutChar,
      TemplateSettings templateSettings) {
    final Document document = editor.getDocument();
    CharSequence text = document.getCharsSequence();
    final int caretOffset = editor.getCaretModel().getOffset();

    List<TemplateImpl> candidatesWithoutArgument =
        findMatchingTemplates(text, caretOffset, shortcutChar, templateSettings, false);

    int argumentOffset = passArgumentBack(text, caretOffset);
    String argument = null;
    if (argumentOffset >= 0) {
      argument = text.subSequence(argumentOffset, caretOffset).toString();
      if (argumentOffset > 0 && text.charAt(argumentOffset - 1) == ' ') {
        if (argumentOffset - 2 >= 0
            && Character.isJavaIdentifierPart(text.charAt(argumentOffset - 2))) {
          argumentOffset--;
        }
      }
    }
    List<TemplateImpl> candidatesWithArgument =
        findMatchingTemplates(text, argumentOffset, shortcutChar, templateSettings, true);

    if (candidatesWithArgument.isEmpty() && candidatesWithoutArgument.isEmpty()) {
      return null;
    }

    candidatesWithoutArgument =
        filterApplicableCandidates(file, caretOffset, candidatesWithoutArgument);
    candidatesWithArgument =
        filterApplicableCandidates(file, argumentOffset, candidatesWithArgument);
    Map<TemplateImpl, String> candidate2Argument = new HashMap<TemplateImpl, String>();
    addToMap(candidate2Argument, candidatesWithoutArgument, null);
    addToMap(candidate2Argument, candidatesWithArgument, argument);
    return candidate2Argument;
  }