1 /* 2 * Copyright 2015 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.googlejavaformat; 16 17 import static com.google.common.collect.Iterables.getLast; 18 import static java.lang.Math.max; 19 20 import com.google.common.base.MoreObjects; 21 import com.google.common.collect.DiscreteDomain; 22 import com.google.common.collect.Iterators; 23 import com.google.common.collect.Range; 24 import com.google.googlejavaformat.Output.BreakTag; 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.Optional; 28 29 /** 30 * {@link com.google.googlejavaformat.java.JavaInputAstVisitor JavaInputAstVisitor} outputs a 31 * sequence of {@link Op}s using {@link OpsBuilder}. This linear sequence is then transformed by 32 * {@link DocBuilder} into a tree-structured {@code Doc}. The top-level {@code Doc} is a {@link 33 * Level}, which contains a sequence of {@code Doc}s, including other {@link Level}s. Leaf {@code 34 * Doc}s are {@link Token}s, representing language-level tokens; {@link Tok}s, which may also 35 * represent non-token {@link Input.Tok}s, including comments and other white-space; {@link Space}s, 36 * representing single spaces; and {@link Break}s, which represent optional line-breaks. 37 */ 38 public abstract class Doc { 39 /** 40 * Each {@link Break} in a {@link Level} is either {@link FillMode#UNIFIED} or {@link 41 * FillMode#INDEPENDENT}. 42 */ 43 public enum FillMode { 44 /** 45 * If a {@link Level} will not fit on one line, all of its {@code UNIFIED} {@link Break}s will 46 * be broken. 47 */ 48 UNIFIED, 49 50 /** 51 * If a {@link Level} will not fit on one line, its {@code INDEPENDENT} {@link Break}s will be 52 * broken independently of each other, to fill in the {@link Level}. 53 */ 54 INDEPENDENT, 55 56 /** 57 * A {@code FORCED} {@link Break} will always be broken, and a {@link Level} it appears in will 58 * not fit on one line. 59 */ 60 FORCED 61 } 62 63 /** State for writing. */ 64 public static final class State { 65 final int lastIndent; 66 final int indent; 67 final int column; 68 final boolean mustBreak; 69 State(int lastIndent, int indent, int column, boolean mustBreak)70 State(int lastIndent, int indent, int column, boolean mustBreak) { 71 this.lastIndent = lastIndent; 72 this.indent = indent; 73 this.column = column; 74 this.mustBreak = mustBreak; 75 } 76 State(int indent0, int column0)77 public State(int indent0, int column0) { 78 this(indent0, indent0, column0, false); 79 } 80 withColumn(int column)81 State withColumn(int column) { 82 return new State(lastIndent, indent, column, mustBreak); 83 } 84 withMustBreak(boolean mustBreak)85 State withMustBreak(boolean mustBreak) { 86 return new State(lastIndent, indent, column, mustBreak); 87 } 88 89 @Override toString()90 public String toString() { 91 return MoreObjects.toStringHelper(this) 92 .add("lastIndent", lastIndent) 93 .add("indent", indent) 94 .add("column", column) 95 .add("mustBreak", mustBreak) 96 .toString(); 97 } 98 } 99 100 private static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1); 101 private static final DiscreteDomain<Integer> INTEGERS = DiscreteDomain.integers(); 102 103 // Memoized width; Float.POSITIVE_INFINITY if contains forced breaks. 104 private boolean widthComputed = false; 105 private float width = 0.0F; 106 107 // Memoized flat; not defined (and never computed) if contains forced breaks. 108 private boolean flatComputed = false; 109 private String flat = ""; 110 111 // Memoized Range. 112 private boolean rangeComputed = false; 113 private Range<Integer> range = EMPTY_RANGE; 114 115 /** 116 * Return the width of a {@code Doc}, or {@code Float.POSITIVE_INFINITY} if it must be broken. 117 * 118 * @return the width 119 */ getWidth()120 final float getWidth() { 121 if (!widthComputed) { 122 width = computeWidth(); 123 widthComputed = true; 124 } 125 return width; 126 } 127 128 /** 129 * Return a {@code Doc}'s flat-string value; not defined (and never called) if the (@code Doc} 130 * contains forced breaks. 131 * 132 * @return the flat-string value 133 */ getFlat()134 final String getFlat() { 135 if (!flatComputed) { 136 flat = computeFlat(); 137 flatComputed = true; 138 } 139 return flat; 140 } 141 142 /** 143 * Return the {@link Range} of a {@code Doc}. 144 * 145 * @return the {@code Doc}'s {@link Range} 146 */ range()147 final Range<Integer> range() { 148 if (!rangeComputed) { 149 range = computeRange(); 150 rangeComputed = true; 151 } 152 return range; 153 } 154 155 /** 156 * Compute the {@code Doc}'s width. 157 * 158 * @return the width, or {@code Float.POSITIVE_INFINITY} if it must be broken 159 */ computeWidth()160 abstract float computeWidth(); 161 162 /** 163 * Compute the {@code Doc}'s flat value. Not defined (and never called) if contains forced breaks. 164 * 165 * @return the flat value 166 */ computeFlat()167 abstract String computeFlat(); 168 169 /** 170 * Compute the {@code Doc}'s {@link Range} of {@link Input.Token}s. 171 * 172 * @return the {@link Range} 173 */ computeRange()174 abstract Range<Integer> computeRange(); 175 176 /** 177 * Make breaking decisions for a {@code Doc}. 178 * 179 * @param maxWidth the maximum line width 180 * @param state the current output state 181 * @return the new output state 182 */ computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)183 public abstract State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state); 184 185 /** Write a {@code Doc} to an {@link Output}, after breaking decisions have been made. */ write(Output output)186 public abstract void write(Output output); 187 188 /** A {@code Level} inside a {@link Doc}. */ 189 static final class Level extends Doc { 190 private final Indent plusIndent; // The extra indent following breaks. 191 private final List<Doc> docs = new ArrayList<>(); // The elements of the level. 192 Level(Indent plusIndent)193 private Level(Indent plusIndent) { 194 this.plusIndent = plusIndent; 195 } 196 197 /** 198 * Factory method for {@code Level}s. 199 * 200 * @param plusIndent the extra indent inside the {@code Level} 201 * @return the new {@code Level} 202 */ make(Indent plusIndent)203 static Level make(Indent plusIndent) { 204 return new Level(plusIndent); 205 } 206 207 /** 208 * Add a {@link Doc} to the {@code Level}. 209 * 210 * @param doc the {@link Doc} to add 211 */ add(Doc doc)212 void add(Doc doc) { 213 docs.add(doc); 214 } 215 216 @Override computeWidth()217 float computeWidth() { 218 float thisWidth = 0.0F; 219 for (Doc doc : docs) { 220 thisWidth += doc.getWidth(); 221 } 222 return thisWidth; 223 } 224 225 @Override computeFlat()226 String computeFlat() { 227 StringBuilder builder = new StringBuilder(); 228 for (Doc doc : docs) { 229 builder.append(doc.getFlat()); 230 } 231 return builder.toString(); 232 } 233 234 @Override computeRange()235 Range<Integer> computeRange() { 236 Range<Integer> docRange = EMPTY_RANGE; 237 for (Doc doc : docs) { 238 docRange = union(docRange, doc.range()); 239 } 240 return docRange; 241 } 242 243 // State that needs to be preserved between calculating breaks and 244 // writing output. 245 // TODO(cushon): represent phases as separate immutable data. 246 247 /** True if the entire {@link Level} fits on one line. */ 248 boolean oneLine = false; 249 250 /** 251 * Groups of {@link Doc}s that are children of the current {@link Level}, separated by {@link 252 * Break}s. 253 */ 254 List<List<Doc>> splits = new ArrayList<>(); 255 256 /** {@link Break}s between {@link Doc}s in the current {@link Level}. */ 257 List<Break> breaks = new ArrayList<>(); 258 259 @Override computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)260 public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { 261 float thisWidth = getWidth(); 262 if (state.column + thisWidth <= maxWidth) { 263 oneLine = true; 264 return state.withColumn(state.column + (int) thisWidth); 265 } 266 State broken = 267 computeBroken( 268 commentsHelper, maxWidth, new State(state.indent + plusIndent.eval(), state.column)); 269 return state.withColumn(broken.column); 270 } 271 splitByBreaks(List<Doc> docs, List<List<Doc>> splits, List<Break> breaks)272 private static void splitByBreaks(List<Doc> docs, List<List<Doc>> splits, List<Break> breaks) { 273 splits.clear(); 274 breaks.clear(); 275 splits.add(new ArrayList<>()); 276 for (Doc doc : docs) { 277 if (doc instanceof Break) { 278 breaks.add((Break) doc); 279 splits.add(new ArrayList<>()); 280 } else { 281 getLast(splits).add(doc); 282 } 283 } 284 } 285 286 /** Compute breaks for a {@link Level} that spans multiple lines. */ computeBroken(CommentsHelper commentsHelper, int maxWidth, State state)287 private State computeBroken(CommentsHelper commentsHelper, int maxWidth, State state) { 288 splitByBreaks(docs, splits, breaks); 289 290 state = 291 computeBreakAndSplit( 292 commentsHelper, maxWidth, state, /* optBreakDoc= */ Optional.empty(), splits.get(0)); 293 294 // Handle following breaks and split. 295 for (int i = 0; i < breaks.size(); i++) { 296 state = 297 computeBreakAndSplit( 298 commentsHelper, maxWidth, state, Optional.of(breaks.get(i)), splits.get(i + 1)); 299 } 300 return state; 301 } 302 303 /** Lay out a Break-separated group of Docs in the current Level. */ computeBreakAndSplit( CommentsHelper commentsHelper, int maxWidth, State state, Optional<Break> optBreakDoc, List<Doc> split)304 private static State computeBreakAndSplit( 305 CommentsHelper commentsHelper, 306 int maxWidth, 307 State state, 308 Optional<Break> optBreakDoc, 309 List<Doc> split) { 310 float breakWidth = optBreakDoc.isPresent() ? optBreakDoc.get().getWidth() : 0.0F; 311 float splitWidth = getWidth(split); 312 boolean shouldBreak = 313 (optBreakDoc.isPresent() && optBreakDoc.get().fillMode == FillMode.UNIFIED) 314 || state.mustBreak 315 || state.column + breakWidth + splitWidth > maxWidth; 316 317 if (optBreakDoc.isPresent()) { 318 state = optBreakDoc.get().computeBreaks(state, state.lastIndent, shouldBreak); 319 } 320 boolean enoughRoom = state.column + splitWidth <= maxWidth; 321 state = computeSplit(commentsHelper, maxWidth, split, state.withMustBreak(false)); 322 if (!enoughRoom) { 323 state = state.withMustBreak(true); // Break after, too. 324 } 325 return state; 326 } 327 computeSplit( CommentsHelper commentsHelper, int maxWidth, List<Doc> docs, State state)328 private static State computeSplit( 329 CommentsHelper commentsHelper, int maxWidth, List<Doc> docs, State state) { 330 for (Doc doc : docs) { 331 state = doc.computeBreaks(commentsHelper, maxWidth, state); 332 } 333 return state; 334 } 335 336 @Override write(Output output)337 public void write(Output output) { 338 if (oneLine) { 339 output.append(getFlat(), range()); // This is defined because width is finite. 340 } else { 341 writeFilled(output); 342 } 343 } 344 writeFilled(Output output)345 private void writeFilled(Output output) { 346 // Handle first split. 347 for (Doc doc : splits.get(0)) { 348 doc.write(output); 349 } 350 // Handle following breaks and split. 351 for (int i = 0; i < breaks.size(); i++) { 352 breaks.get(i).write(output); 353 for (Doc doc : splits.get(i + 1)) { 354 doc.write(output); 355 } 356 } 357 } 358 359 /** 360 * Get the width of a sequence of {@link Doc}s. 361 * 362 * @param docs the {@link Doc}s 363 * @return the width, or {@code Float.POSITIVE_INFINITY} if any {@link Doc} must be broken 364 */ getWidth(List<Doc> docs)365 static float getWidth(List<Doc> docs) { 366 float width = 0.0F; 367 for (Doc doc : docs) { 368 width += doc.getWidth(); 369 } 370 return width; 371 } 372 union(Range<Integer> x, Range<Integer> y)373 private static Range<Integer> union(Range<Integer> x, Range<Integer> y) { 374 return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(INTEGERS); 375 } 376 377 @Override toString()378 public String toString() { 379 return MoreObjects.toStringHelper(this) 380 .add("plusIndent", plusIndent) 381 .add("docs", docs) 382 .toString(); 383 } 384 } 385 386 /** A leaf {@link Doc} for a token. */ 387 public static final class Token extends Doc implements Op { 388 /** Is a Token a real token, or imaginary (e.g., a token generated incorrectly, or an EOF)? */ 389 public enum RealOrImaginary { 390 REAL, 391 IMAGINARY; 392 isReal()393 boolean isReal() { 394 return this == REAL; 395 } 396 } 397 398 private final Input.Token token; 399 private final RealOrImaginary realOrImaginary; 400 private final Indent plusIndentCommentsBefore; 401 private final Optional<Indent> breakAndIndentTrailingComment; 402 Token( Input.Token token, RealOrImaginary realOrImaginary, Indent plusIndentCommentsBefore, Optional<Indent> breakAndIndentTrailingComment)403 private Token( 404 Input.Token token, 405 RealOrImaginary realOrImaginary, 406 Indent plusIndentCommentsBefore, 407 Optional<Indent> breakAndIndentTrailingComment) { 408 this.token = token; 409 this.realOrImaginary = realOrImaginary; 410 this.plusIndentCommentsBefore = plusIndentCommentsBefore; 411 this.breakAndIndentTrailingComment = breakAndIndentTrailingComment; 412 } 413 414 /** 415 * How much extra to indent comments before the {@code Token}. 416 * 417 * @return the extra indent 418 */ getPlusIndentCommentsBefore()419 Indent getPlusIndentCommentsBefore() { 420 return plusIndentCommentsBefore; 421 } 422 423 /** Force a line break and indent trailing javadoc or block comments. */ breakAndIndentTrailingComment()424 Optional<Indent> breakAndIndentTrailingComment() { 425 return breakAndIndentTrailingComment; 426 } 427 428 /** 429 * Make a {@code Token}. 430 * 431 * @param token the {@link Input.Token} to wrap 432 * @param realOrImaginary did this {@link Input.Token} appear in the input, or was it generated 433 * incorrectly? 434 * @param plusIndentCommentsBefore extra {@code plusIndent} for comments just before this token 435 * @return the new {@code Token} 436 */ make( Input.Token token, Doc.Token.RealOrImaginary realOrImaginary, Indent plusIndentCommentsBefore, Optional<Indent> breakAndIndentTrailingComment)437 static Op make( 438 Input.Token token, 439 Doc.Token.RealOrImaginary realOrImaginary, 440 Indent plusIndentCommentsBefore, 441 Optional<Indent> breakAndIndentTrailingComment) { 442 return new Token( 443 token, realOrImaginary, plusIndentCommentsBefore, breakAndIndentTrailingComment); 444 } 445 446 /** 447 * Return the wrapped {@link Input.Token}. 448 * 449 * @return the {@link Input.Token} 450 */ getToken()451 Input.Token getToken() { 452 return token; 453 } 454 455 /** 456 * Is the token good? That is, does it match an {@link Input.Token}? 457 * 458 * @return whether the @code Token} is good 459 */ realOrImaginary()460 RealOrImaginary realOrImaginary() { 461 return realOrImaginary; 462 } 463 464 @Override add(DocBuilder builder)465 public void add(DocBuilder builder) { 466 builder.add(this); 467 } 468 469 @Override computeWidth()470 float computeWidth() { 471 return token.getTok().length(); 472 } 473 474 @Override computeFlat()475 String computeFlat() { 476 return token.getTok().getOriginalText(); 477 } 478 479 @Override computeRange()480 Range<Integer> computeRange() { 481 return Range.singleton(token.getTok().getIndex()).canonical(INTEGERS); 482 } 483 484 @Override computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)485 public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { 486 String text = token.getTok().getOriginalText(); 487 return state.withColumn(state.column + text.length()); 488 } 489 490 @Override write(Output output)491 public void write(Output output) { 492 String text = token.getTok().getOriginalText(); 493 output.append(text, range()); 494 } 495 496 @Override toString()497 public String toString() { 498 return MoreObjects.toStringHelper(this) 499 .add("token", token) 500 .add("realOrImaginary", realOrImaginary) 501 .add("plusIndentCommentsBefore", plusIndentCommentsBefore) 502 .toString(); 503 } 504 } 505 506 /** A Leaf node in a {@link Doc} for a non-breaking space. */ 507 static final class Space extends Doc implements Op { 508 private static final Space SPACE = new Space(); 509 Space()510 private Space() {} 511 512 /** 513 * Factor method for {@code Space}. 514 * 515 * @return the new {@code Space} 516 */ make()517 static Space make() { 518 return SPACE; 519 } 520 521 @Override add(DocBuilder builder)522 public void add(DocBuilder builder) { 523 builder.add(this); 524 } 525 526 @Override computeWidth()527 float computeWidth() { 528 return 1.0F; 529 } 530 531 @Override computeFlat()532 String computeFlat() { 533 return " "; 534 } 535 536 @Override computeRange()537 Range<Integer> computeRange() { 538 return EMPTY_RANGE; 539 } 540 541 @Override computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)542 public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { 543 return state.withColumn(state.column + 1); 544 } 545 546 @Override write(Output output)547 public void write(Output output) { 548 output.append(" ", range()); 549 } 550 551 @Override toString()552 public String toString() { 553 return MoreObjects.toStringHelper(this).toString(); 554 } 555 } 556 557 /** A leaf node in a {@link Doc} for an optional break. */ 558 public static final class Break extends Doc implements Op { 559 private final FillMode fillMode; 560 private final String flat; 561 private final Indent plusIndent; 562 private final Optional<BreakTag> optTag; 563 Break(FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag)564 private Break(FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) { 565 this.fillMode = fillMode; 566 this.flat = flat; 567 this.plusIndent = plusIndent; 568 this.optTag = optTag; 569 } 570 571 /** 572 * Make a {@code Break}. 573 * 574 * @param fillMode the {@link FillMode} 575 * @param flat the text when not broken 576 * @param plusIndent extra indent if taken 577 * @return the new {@code Break} 578 */ make(FillMode fillMode, String flat, Indent plusIndent)579 public static Break make(FillMode fillMode, String flat, Indent plusIndent) { 580 return new Break(fillMode, flat, plusIndent, /* optTag= */ Optional.empty()); 581 } 582 583 /** 584 * Make a {@code Break}. 585 * 586 * @param fillMode the {@link FillMode} 587 * @param flat the text when not broken 588 * @param plusIndent extra indent if taken 589 * @param optTag an optional tag for remembering whether the break was taken 590 * @return the new {@code Break} 591 */ make( FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag)592 public static Break make( 593 FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) { 594 return new Break(fillMode, flat, plusIndent, optTag); 595 } 596 597 /** 598 * Make a forced {@code Break}. 599 * 600 * @return the new forced {@code Break} 601 */ makeForced()602 public static Break makeForced() { 603 return make(FillMode.FORCED, "", Indent.Const.ZERO); 604 } 605 606 /** 607 * Return the {@code Break}'s extra indent. 608 * 609 * @return the extra indent 610 */ getPlusIndent()611 int getPlusIndent() { 612 return plusIndent.eval(); 613 } 614 615 /** 616 * Is the {@code Break} forced? 617 * 618 * @return whether the {@code Break} is forced 619 */ isForced()620 boolean isForced() { 621 return fillMode == FillMode.FORCED; 622 } 623 624 @Override add(DocBuilder builder)625 public void add(DocBuilder builder) { 626 builder.breakDoc(this); 627 } 628 629 @Override computeWidth()630 float computeWidth() { 631 return isForced() ? Float.POSITIVE_INFINITY : (float) flat.length(); 632 } 633 634 @Override computeFlat()635 String computeFlat() { 636 return flat; 637 } 638 639 @Override computeRange()640 Range<Integer> computeRange() { 641 return EMPTY_RANGE; 642 } 643 644 /** Was this break taken? */ 645 boolean broken; 646 647 /** New indent after this break. */ 648 int newIndent; 649 computeBreaks(State state, int lastIndent, boolean broken)650 public State computeBreaks(State state, int lastIndent, boolean broken) { 651 if (optTag.isPresent()) { 652 optTag.get().recordBroken(broken); 653 } 654 655 if (broken) { 656 this.broken = true; 657 this.newIndent = max(lastIndent + plusIndent.eval(), 0); 658 return state.withColumn(newIndent); 659 } else { 660 this.broken = false; 661 this.newIndent = -1; 662 return state.withColumn(state.column + flat.length()); 663 } 664 } 665 666 @Override computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)667 public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { 668 // Updating the state for {@link Break}s requires deciding if the break 669 // should be taken. 670 // TODO(cushon): this hierarchy is wrong, create a separate interface 671 // for unbreakable Docs? 672 throw new UnsupportedOperationException("Did you mean computeBreaks(State, int, boolean)?"); 673 } 674 675 @Override write(Output output)676 public void write(Output output) { 677 if (broken) { 678 output.append("\n", EMPTY_RANGE); 679 output.indent(newIndent); 680 } else { 681 output.append(flat, range()); 682 } 683 } 684 685 @Override toString()686 public String toString() { 687 return MoreObjects.toStringHelper(this) 688 .add("fillMode", fillMode) 689 .add("flat", flat) 690 .add("plusIndent", plusIndent) 691 .add("optTag", optTag) 692 .toString(); 693 } 694 } 695 696 /** A leaf node in a {@link Doc} for a non-token. */ 697 static final class Tok extends Doc implements Op { 698 private final Input.Tok tok; 699 Tok(Input.Tok tok)700 private Tok(Input.Tok tok) { 701 this.tok = tok; 702 } 703 704 /** 705 * Factory method for a {@code Tok}. 706 * 707 * @param tok the {@link Input.Tok} to wrap 708 * @return the new {@code Tok} 709 */ make(Input.Tok tok)710 static Tok make(Input.Tok tok) { 711 return new Tok(tok); 712 } 713 714 @Override add(DocBuilder builder)715 public void add(DocBuilder builder) { 716 builder.add(this); 717 } 718 719 @Override computeWidth()720 float computeWidth() { 721 int idx = Newlines.firstBreak(tok.getOriginalText()); 722 // only count the first line of multi-line block comments 723 if (tok.isComment()) { 724 if (idx > 0) { 725 return idx; 726 } else if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) { 727 // Account for line comments with missing spaces, see computeFlat. 728 return tok.length() + 1; 729 } else { 730 return tok.length(); 731 } 732 } 733 return idx != -1 ? Float.POSITIVE_INFINITY : (float) tok.length(); 734 } 735 736 @Override computeFlat()737 String computeFlat() { 738 // TODO(cushon): commentsHelper.rewrite doesn't get called for spans that fit in a single 739 // line. That's fine for multi-line comment reflowing, but problematic for adding missing 740 // spaces in line comments. 741 if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) { 742 return "// " + tok.getOriginalText().substring("//".length()); 743 } 744 return tok.getOriginalText(); 745 } 746 747 @Override computeRange()748 Range<Integer> computeRange() { 749 return Range.singleton(tok.getIndex()).canonical(INTEGERS); 750 } 751 752 String text; 753 754 @Override computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)755 public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { 756 text = commentsHelper.rewrite(tok, maxWidth, state.column); 757 int firstLineLength = text.length() - Iterators.getLast(Newlines.lineOffsetIterator(text)); 758 return state.withColumn(state.column + firstLineLength); 759 } 760 761 @Override write(Output output)762 public void write(Output output) { 763 output.append(text, range()); 764 } 765 766 @Override toString()767 public String toString() { 768 return MoreObjects.toStringHelper(this).add("tok", tok).toString(); 769 } 770 } 771 } 772