public TextRange surroundExpression(
     final Project project, final Editor editor, PsiExpression expr)
     throws IncorrectOperationException {
   assert expr.isValid();
   PsiType[] types = GuessManager.getInstance(project).guessTypeToCast(expr);
   final boolean parenthesesNeeded =
       expr instanceof PsiPolyadicExpression
           || expr instanceof PsiConditionalExpression
           || expr instanceof PsiAssignmentExpression;
   String exprText = parenthesesNeeded ? "(" + expr.getText() + ")" : expr.getText();
   final Template template = generateTemplate(project, exprText, types);
   TextRange range;
   if (expr.isPhysical()) {
     range = expr.getTextRange();
   } else {
     final RangeMarker rangeMarker = expr.getUserData(ElementToWorkOn.TEXT_RANGE);
     if (rangeMarker == null) return null;
     range = new TextRange(rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
   }
   editor.getDocument().deleteString(range.getStartOffset(), range.getEndOffset());
   editor.getCaretModel().moveToOffset(range.getStartOffset());
   editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
   TemplateManager.getInstance(project).startTemplate(editor, template);
   return null;
 }
  private static Template generateTemplate(
      Project project, String exprText, final PsiType[] suggestedTypes) {
    final TemplateManager templateManager = TemplateManager.getInstance(project);
    final Template template = templateManager.createTemplate("", "");
    template.setToReformat(true);

    Set<LookupElement> itemSet = new LinkedHashSet<>();
    for (PsiType type : suggestedTypes) {
      itemSet.add(PsiTypeLookupItem.createLookupItem(type, null));
    }
    final LookupElement[] lookupItems = itemSet.toArray(new LookupElement[itemSet.size()]);

    final Result result =
        suggestedTypes.length > 0 ? new PsiTypeResult(suggestedTypes[0], project) : null;

    Expression expr =
        new Expression() {
          @Override
          public LookupElement[] calculateLookupItems(ExpressionContext context) {
            return lookupItems.length > 1 ? lookupItems : null;
          }

          @Override
          public Result calculateResult(ExpressionContext context) {
            return result;
          }

          @Override
          public Result calculateQuickResult(ExpressionContext context) {
            return null;
          }
        };
    template.addTextSegment("((");
    template.addVariable(TYPE_TEMPLATE_VARIABLE, expr, expr, true);
    template.addTextSegment(")" + exprText + ")");
    template.addEndVariable();

    return template;
  }
  public static void main(String[] args) {
    TemplateManager tm = TemplateManager.getInstance();

    TemplateTree root = new TemplateTree(tm.get("word[0]"));

    TemplateTree t2 = new TemplateTree(tm.get("pos[-1]"));
    root.addChild(t2);
    t2.addChild(new Features());

    TemplateTree t3 = new TemplateTree(tm.get("pos[0]"));
    root.addChild(t3);
    t3.addChild(new Features());

    TemplateTree t4 = new TemplateTree(tm.get("pos[-1]"));
    t2.addChild(t4);
    t4.addChild(new Features());

    Document doc = DocumentTester.getMockDocument();
    Context ctx = new Context(doc, 1);
    ctx.token = 2;
    root.apply(ctx);
    System.out.println(ctx);
  }