/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java

https://bitbucket.org/nbargnesi/idea · Java · 807 lines · 665 code · 87 blank · 55 comment · 79 complexity · fa5dacb401b690da365012e60bb3b0f9 MD5 · raw file

  1. /*
  2. * Copyright 2000-2012 JetBrains s.r.o.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.intellij.formatting;
  17. import com.intellij.openapi.application.Application;
  18. import com.intellij.openapi.application.ApplicationManager;
  19. import com.intellij.openapi.components.ApplicationComponent;
  20. import com.intellij.openapi.diagnostic.Logger;
  21. import com.intellij.openapi.fileTypes.FileType;
  22. import com.intellij.openapi.progress.ProgressManager;
  23. import com.intellij.openapi.util.Computable;
  24. import com.intellij.openapi.util.TextRange;
  25. import com.intellij.psi.PsiFile;
  26. import com.intellij.psi.codeStyle.CodeStyleSettings;
  27. import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
  28. import com.intellij.psi.formatter.FormatterUtil;
  29. import com.intellij.psi.formatter.FormattingDocumentModelImpl;
  30. import com.intellij.psi.formatter.PsiBasedFormattingModel;
  31. import com.intellij.util.IncorrectOperationException;
  32. import com.intellij.util.SequentialTask;
  33. import com.intellij.util.text.CharArrayUtil;
  34. import org.jetbrains.annotations.NotNull;
  35. import org.jetbrains.annotations.Nullable;
  36. import java.util.HashMap;
  37. import java.util.Map;
  38. import java.util.concurrent.atomic.AtomicInteger;
  39. import java.util.concurrent.atomic.AtomicReference;
  40. public class FormatterImpl extends FormatterEx
  41. implements ApplicationComponent,
  42. IndentFactory,
  43. WrapFactory,
  44. AlignmentFactory,
  45. SpacingFactory,
  46. FormattingModelFactory
  47. {
  48. private static final Logger LOG = Logger.getInstance("#com.intellij.formatting.FormatterImpl");
  49. private final AtomicReference<FormattingProgressTask> myProgressTask = new AtomicReference<FormattingProgressTask>();
  50. private final AtomicInteger myIsDisabledCount = new AtomicInteger();
  51. private final IndentImpl NONE_INDENT = new IndentImpl(Indent.Type.NONE, false, false);
  52. private final IndentImpl myAbsoluteNoneIndent = new IndentImpl(Indent.Type.NONE, true, false);
  53. private final IndentImpl myLabelIndent = new IndentImpl(Indent.Type.LABEL, false, false);
  54. private final IndentImpl myContinuationIndentRelativeToDirectParent = new IndentImpl(Indent.Type.CONTINUATION, false, true);
  55. private final IndentImpl myContinuationIndentNotRelativeToDirectParent = new IndentImpl(Indent.Type.CONTINUATION, false, false);
  56. private final IndentImpl myContinuationWithoutFirstIndentRelativeToDirectParent
  57. = new IndentImpl(Indent.Type.CONTINUATION_WITHOUT_FIRST, false, true);
  58. private final IndentImpl myContinuationWithoutFirstIndentNotRelativeToDirectParent
  59. = new IndentImpl(Indent.Type.CONTINUATION_WITHOUT_FIRST, false, false);
  60. private final IndentImpl myAbsoluteLabelIndent = new IndentImpl(Indent.Type.LABEL, true, false);
  61. private final IndentImpl myNormalIndentRelativeToDirectParent = new IndentImpl(Indent.Type.NORMAL, false, true);
  62. private final IndentImpl myNormalIndentNotRelativeToDirectParent = new IndentImpl(Indent.Type.NORMAL, false, false);
  63. private final SpacingImpl myReadOnlySpacing = new SpacingImpl(0, 0, 0, true, false, true, 0, false, 0);
  64. public FormatterImpl() {
  65. Indent.setFactory(this);
  66. Wrap.setFactory(this);
  67. Alignment.setFactory(this);
  68. Spacing.setFactory(this);
  69. FormattingModelProvider.setFactory(this);
  70. }
  71. public Alignment createAlignment(boolean applyToNonFirstBlocksOnLine, @NotNull Alignment.Anchor anchor) {
  72. return new AlignmentImpl(applyToNonFirstBlocksOnLine, anchor);
  73. }
  74. public Alignment createChildAlignment(final Alignment base) {
  75. AlignmentImpl result = new AlignmentImpl();
  76. result.setParent(base);
  77. return result;
  78. }
  79. public Indent getNormalIndent(boolean relative) {
  80. return relative ? myNormalIndentRelativeToDirectParent : myNormalIndentNotRelativeToDirectParent;
  81. }
  82. public Indent getNoneIndent() {
  83. return NONE_INDENT;
  84. }
  85. @Override
  86. public void setProgressTask(@NotNull FormattingProgressTask progressIndicator) {
  87. if (!FormatterUtil.isFormatterCalledExplicitly()) {
  88. return;
  89. }
  90. myProgressTask.set(progressIndicator);
  91. }
  92. public void format(final FormattingModel model, final CodeStyleSettings settings,
  93. final CommonCodeStyleSettings.IndentOptions indentOptions,
  94. final CommonCodeStyleSettings.IndentOptions javaIndentOptions,
  95. final FormatTextRanges affectedRanges) throws IncorrectOperationException
  96. {
  97. SequentialTask task = new MyFormattingTask() {
  98. @NotNull
  99. @Override
  100. protected FormatProcessor buildProcessor() {
  101. FormatProcessor processor = new FormatProcessor(
  102. model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, affectedRanges, FormattingProgressCallback.EMPTY
  103. );
  104. processor.setJavaIndentOptions(javaIndentOptions);
  105. processor.format(model);
  106. return processor;
  107. }
  108. };
  109. execute(task);
  110. }
  111. public Wrap createWrap(WrapType type, boolean wrapFirstElement) {
  112. return new WrapImpl(type, wrapFirstElement);
  113. }
  114. public Wrap createChildWrap(final Wrap parentWrap, final WrapType wrapType, final boolean wrapFirstElement) {
  115. final WrapImpl result = new WrapImpl(wrapType, wrapFirstElement);
  116. result.registerParent((WrapImpl)parentWrap);
  117. return result;
  118. }
  119. @NotNull
  120. public Spacing createSpacing(int minOffset,
  121. int maxOffset,
  122. int minLineFeeds,
  123. final boolean keepLineBreaks,
  124. final int keepBlankLines) {
  125. return getSpacingImpl(minOffset, maxOffset, minLineFeeds, false, false, keepLineBreaks, keepBlankLines,false, 0);
  126. }
  127. @NotNull
  128. public Spacing getReadOnlySpacing() {
  129. return myReadOnlySpacing;
  130. }
  131. @NotNull
  132. @Override
  133. public Spacing createDependentLFSpacing(int minSpaces,
  134. int maxSpaces,
  135. @NotNull TextRange dependencyRange,
  136. boolean keepLineBreaks,
  137. int keepBlankLines,
  138. @NotNull DependentSpacingRule rule)
  139. {
  140. return new DependantSpacingImpl(minSpaces, maxSpaces, dependencyRange, keepLineBreaks, keepBlankLines, rule);
  141. }
  142. @NotNull
  143. private FormattingProgressCallback getProgressCallback() {
  144. FormattingProgressCallback result = myProgressTask.get();
  145. return result == null ? FormattingProgressCallback.EMPTY : result;
  146. }
  147. public void format(final FormattingModel model,
  148. final CodeStyleSettings settings,
  149. final CommonCodeStyleSettings.IndentOptions indentOptions,
  150. final FormatTextRanges affectedRanges) throws IncorrectOperationException {
  151. SequentialTask task = new MyFormattingTask() {
  152. @NotNull
  153. @Override
  154. protected FormatProcessor buildProcessor() {
  155. FormatProcessor processor = new FormatProcessor(
  156. model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, affectedRanges, getProgressCallback()
  157. );
  158. processor.format(model, true);
  159. return processor;
  160. }
  161. };
  162. execute(task);
  163. }
  164. public void formatWithoutModifications(final FormattingDocumentModel model,
  165. final Block rootBlock,
  166. final CodeStyleSettings settings,
  167. final CommonCodeStyleSettings.IndentOptions indentOptions,
  168. final TextRange affectedRange) throws IncorrectOperationException
  169. {
  170. SequentialTask task = new MyFormattingTask() {
  171. @NotNull
  172. @Override
  173. protected FormatProcessor buildProcessor() {
  174. FormatProcessor result = new FormatProcessor(
  175. model, rootBlock, settings, indentOptions, new FormatTextRanges(affectedRange, true), FormattingProgressCallback.EMPTY
  176. );
  177. result.formatWithoutRealModifications();
  178. return result;
  179. }
  180. };
  181. execute(task);
  182. }
  183. /**
  184. * Execute given sequential formatting task. Two approaches are possible:
  185. * <pre>
  186. * <ul>
  187. * <li>
  188. * <b>synchronous</b> - the task is completely executed during the current method processing;
  189. * </li>
  190. * <li>
  191. * <b>asynchronous</b> - the task is executed at background thread under the progress dialog;
  192. * </li>
  193. * </ul>
  194. * </pre>
  195. *
  196. * @param task task to execute
  197. */
  198. private void execute(@NotNull SequentialTask task) {
  199. disableFormatting();
  200. Application application = ApplicationManager.getApplication();
  201. FormattingProgressTask progressTask = myProgressTask.getAndSet(null);
  202. if (progressTask == null || !application.isDispatchThread() || application.isUnitTestMode()) {
  203. try {
  204. task.prepare();
  205. while (!task.isDone()) {
  206. task.iteration();
  207. }
  208. }
  209. finally {
  210. enableFormatting();
  211. }
  212. }
  213. else {
  214. progressTask.setTask(task);
  215. Runnable callback = new Runnable() {
  216. @Override
  217. public void run() {
  218. enableFormatting();
  219. }
  220. };
  221. for (FormattingProgressCallback.EventType eventType : FormattingProgressCallback.EventType.values()) {
  222. progressTask.addCallback(eventType, callback);
  223. }
  224. ProgressManager.getInstance().run(progressTask);
  225. }
  226. }
  227. public IndentInfo getWhiteSpaceBefore(final FormattingDocumentModel model,
  228. final Block block,
  229. final CodeStyleSettings settings,
  230. final CommonCodeStyleSettings.IndentOptions indentOptions,
  231. final TextRange affectedRange, final boolean mayChangeLineFeeds)
  232. {
  233. disableFormatting();
  234. try {
  235. final FormatProcessor processor = buildProcessorAndWrapBlocks(
  236. model, block, settings, indentOptions, new FormatTextRanges(affectedRange, true)
  237. );
  238. final LeafBlockWrapper blockBefore = processor.getBlockAfter(affectedRange.getStartOffset());
  239. LOG.assertTrue(blockBefore != null);
  240. WhiteSpace whiteSpace = blockBefore.getWhiteSpace();
  241. LOG.assertTrue(whiteSpace != null);
  242. if (!mayChangeLineFeeds) {
  243. whiteSpace.setLineFeedsAreReadOnly();
  244. }
  245. processor.setAllWhiteSpacesAreReadOnly();
  246. whiteSpace.setReadOnly(false);
  247. processor.formatWithoutRealModifications();
  248. return new IndentInfo(whiteSpace.getLineFeeds(), whiteSpace.getIndentOffset(), whiteSpace.getSpaces());
  249. }
  250. finally {
  251. enableFormatting();
  252. }
  253. }
  254. public void adjustLineIndentsForRange(final FormattingModel model,
  255. final CodeStyleSettings settings,
  256. final CommonCodeStyleSettings.IndentOptions indentOptions,
  257. final TextRange rangeToAdjust) {
  258. disableFormatting();
  259. try {
  260. final FormattingDocumentModel documentModel = model.getDocumentModel();
  261. final Block block = model.getRootBlock();
  262. final FormatProcessor processor = buildProcessorAndWrapBlocks(
  263. documentModel, block, settings, indentOptions, new FormatTextRanges(rangeToAdjust, true)
  264. );
  265. LeafBlockWrapper tokenBlock = processor.getFirstTokenBlock();
  266. while (tokenBlock != null) {
  267. final WhiteSpace whiteSpace = tokenBlock.getWhiteSpace();
  268. whiteSpace.setLineFeedsAreReadOnly(true);
  269. if (!whiteSpace.containsLineFeeds()) {
  270. whiteSpace.setIsReadOnly(true);
  271. }
  272. tokenBlock = tokenBlock.getNextBlock();
  273. }
  274. processor.formatWithoutRealModifications();
  275. processor.performModifications(model);
  276. }
  277. finally {
  278. enableFormatting();
  279. }
  280. }
  281. public void formatAroundRange(final FormattingModel model,
  282. final CodeStyleSettings settings,
  283. final TextRange textRange,
  284. final FileType fileType) {
  285. disableFormatting();
  286. try {
  287. final FormattingDocumentModel documentModel = model.getDocumentModel();
  288. final Block block = model.getRootBlock();
  289. final FormatProcessor processor = buildProcessorAndWrapBlocks(
  290. documentModel, block, settings, settings.getIndentOptions(fileType), null
  291. );
  292. LeafBlockWrapper tokenBlock = processor.getFirstTokenBlock();
  293. while (tokenBlock != null) {
  294. final WhiteSpace whiteSpace = tokenBlock.getWhiteSpace();
  295. if (whiteSpace.getEndOffset() < textRange.getStartOffset() || whiteSpace.getEndOffset() > textRange.getEndOffset() + 1) {
  296. whiteSpace.setIsReadOnly(true);
  297. } else if (whiteSpace.getStartOffset() > textRange.getStartOffset() &&
  298. whiteSpace.getEndOffset() < textRange.getEndOffset())
  299. {
  300. if (whiteSpace.containsLineFeeds()) {
  301. whiteSpace.setLineFeedsAreReadOnly(true);
  302. } else {
  303. whiteSpace.setIsReadOnly(true);
  304. }
  305. }
  306. tokenBlock = tokenBlock.getNextBlock();
  307. }
  308. processor.formatWithoutRealModifications();
  309. processor.performModifications(model);
  310. }
  311. finally{
  312. enableFormatting();
  313. }
  314. }
  315. public int adjustLineIndent(final FormattingModel model,
  316. final CodeStyleSettings settings,
  317. final CommonCodeStyleSettings.IndentOptions indentOptions,
  318. final int offset,
  319. final TextRange affectedRange) throws IncorrectOperationException {
  320. disableFormatting();
  321. if (model instanceof PsiBasedFormattingModel) {
  322. ((PsiBasedFormattingModel)model).canModifyAllWhiteSpaces();
  323. }
  324. try {
  325. final FormattingDocumentModel documentModel = model.getDocumentModel();
  326. final Block block = model.getRootBlock();
  327. final FormatProcessor processor = buildProcessorAndWrapBlocks(
  328. documentModel, block, settings, indentOptions, new FormatTextRanges(affectedRange, true), offset
  329. );
  330. final LeafBlockWrapper blockAfterOffset = processor.getBlockAfter(offset);
  331. if (blockAfterOffset != null && blockAfterOffset.contains(offset)) {
  332. return offset;
  333. }
  334. WhiteSpace whiteSpace = blockAfterOffset != null ? blockAfterOffset.getWhiteSpace() : processor.getLastWhiteSpace();
  335. return adjustLineIndent(offset, documentModel, processor, indentOptions, model, whiteSpace);
  336. }
  337. finally {
  338. enableFormatting();
  339. }
  340. }
  341. /**
  342. * Delegates to
  343. * {@link #buildProcessorAndWrapBlocks(FormattingDocumentModel, Block, CodeStyleSettings, CommonCodeStyleSettings.IndentOptions, FormatTextRanges, int)}
  344. * with '-1' as an interested offset.
  345. *
  346. * @param docModel
  347. * @param rootBlock
  348. * @param settings
  349. * @param indentOptions
  350. * @param affectedRanges
  351. * @return
  352. */
  353. private static FormatProcessor buildProcessorAndWrapBlocks(final FormattingDocumentModel docModel,
  354. Block rootBlock,
  355. CodeStyleSettings settings,
  356. CommonCodeStyleSettings.IndentOptions indentOptions,
  357. @Nullable FormatTextRanges affectedRanges)
  358. {
  359. return buildProcessorAndWrapBlocks(docModel, rootBlock, settings, indentOptions, affectedRanges, -1);
  360. }
  361. /**
  362. * Builds {@link FormatProcessor} instance and asks it to wrap all {@link Block code blocks}
  363. * {@link FormattingModel#getRootBlock() derived from the given model}.
  364. *
  365. * @param docModel target model
  366. * @param rootBlock root block to process
  367. * @param settings code style settings to use
  368. * @param indentOptions indent options to use
  369. * @param affectedRanges ranges to reformat
  370. * @param interestingOffset interesting offset; <code>'-1'</code> if no particular offset has a special interest
  371. * @return format processor instance with wrapped {@link Block code blocks}
  372. */
  373. @SuppressWarnings({"StatementWithEmptyBody"})
  374. private static FormatProcessor buildProcessorAndWrapBlocks(final FormattingDocumentModel docModel,
  375. Block rootBlock,
  376. CodeStyleSettings settings,
  377. CommonCodeStyleSettings.IndentOptions indentOptions,
  378. @Nullable FormatTextRanges affectedRanges,
  379. int interestingOffset)
  380. {
  381. FormatProcessor processor = new FormatProcessor(
  382. docModel, rootBlock, settings, indentOptions, affectedRanges, interestingOffset, FormattingProgressCallback.EMPTY
  383. );
  384. while (!processor.iteration()) ;
  385. return processor;
  386. }
  387. private static int adjustLineIndent(
  388. final int offset,
  389. final FormattingDocumentModel documentModel,
  390. final FormatProcessor processor,
  391. final CommonCodeStyleSettings.IndentOptions indentOptions,
  392. final FormattingModel model,
  393. final WhiteSpace whiteSpace)
  394. {
  395. boolean wsContainsCaret = whiteSpace.getStartOffset() <= offset && offset < whiteSpace.getEndOffset();
  396. int lineStartOffset = getLineStartOffset(offset, whiteSpace, documentModel);
  397. final IndentInfo indent = calcIndent(offset, documentModel, processor, whiteSpace);
  398. final String newWS = whiteSpace.generateWhiteSpace(indentOptions, lineStartOffset, indent).toString();
  399. if (!whiteSpace.equalsToString(newWS)) {
  400. try {
  401. model.replaceWhiteSpace(whiteSpace.getTextRange(), newWS);
  402. }
  403. finally {
  404. model.commitChanges();
  405. }
  406. }
  407. final int defaultOffset = offset - whiteSpace.getLength() + newWS.length();
  408. if (wsContainsCaret) {
  409. final int ws = whiteSpace.getStartOffset()
  410. + CharArrayUtil.shiftForward(newWS, Math.max(0, lineStartOffset - whiteSpace.getStartOffset()), " \t");
  411. return Math.max(defaultOffset, ws);
  412. } else {
  413. return defaultOffset;
  414. }
  415. }
  416. private static boolean hasContentAfterLineBreak(final FormattingDocumentModel documentModel, final int offset, final WhiteSpace whiteSpace) {
  417. return documentModel.getLineNumber(offset) == documentModel.getLineNumber(whiteSpace.getEndOffset()) &&
  418. documentModel.getTextLength() != offset;
  419. }
  420. public String getLineIndent(final FormattingModel model,
  421. final CodeStyleSettings settings,
  422. final CommonCodeStyleSettings.IndentOptions indentOptions,
  423. final int offset,
  424. final TextRange affectedRange) {
  425. final FormattingDocumentModel documentModel = model.getDocumentModel();
  426. final Block block = model.getRootBlock();
  427. final FormatProcessor processor = buildProcessorAndWrapBlocks(
  428. documentModel, block, settings, indentOptions, new FormatTextRanges(affectedRange, true), offset
  429. );
  430. final LeafBlockWrapper blockAfterOffset = processor.getBlockAfter(offset);
  431. if (blockAfterOffset != null) {
  432. final WhiteSpace whiteSpace = blockAfterOffset.getWhiteSpace();
  433. final IndentInfo indent = calcIndent(offset, documentModel, processor, whiteSpace);
  434. return indent.generateNewWhiteSpace(indentOptions);
  435. }
  436. return null;
  437. }
  438. private static IndentInfo calcIndent(int offset, FormattingDocumentModel documentModel, FormatProcessor processor, WhiteSpace whiteSpace) {
  439. processor.setAllWhiteSpacesAreReadOnly();
  440. whiteSpace.setLineFeedsAreReadOnly(true);
  441. final IndentInfo indent;
  442. if (hasContentAfterLineBreak(documentModel, offset, whiteSpace)) {
  443. whiteSpace.setReadOnly(false);
  444. processor.formatWithoutRealModifications();
  445. indent = new IndentInfo(0, whiteSpace.getIndentOffset(), whiteSpace.getSpaces());
  446. }
  447. else {
  448. indent = processor.getIndentAt(offset);
  449. }
  450. return indent;
  451. }
  452. public static String getText(final FormattingDocumentModel documentModel) {
  453. return getCharSequence(documentModel).toString();
  454. }
  455. private static CharSequence getCharSequence(final FormattingDocumentModel documentModel) {
  456. return documentModel.getText(new TextRange(0, documentModel.getTextLength()));
  457. }
  458. private static int getLineStartOffset(final int offset,
  459. final WhiteSpace whiteSpace,
  460. final FormattingDocumentModel documentModel) {
  461. int lineStartOffset = offset;
  462. CharSequence text = getCharSequence(documentModel);
  463. lineStartOffset = CharArrayUtil.shiftBackwardUntil(text, lineStartOffset, " \t\n");
  464. if (lineStartOffset > whiteSpace.getStartOffset()) {
  465. if (lineStartOffset >= text.length()) lineStartOffset = text.length() - 1;
  466. final int wsStart = whiteSpace.getStartOffset();
  467. int prevEnd;
  468. if (text.charAt(lineStartOffset) == '\n'
  469. && wsStart <= (prevEnd = documentModel.getLineStartOffset(documentModel.getLineNumber(lineStartOffset - 1))) &&
  470. documentModel.getText(new TextRange(prevEnd, lineStartOffset)).toString().trim().length() == 0 // ws consists of space only, it is not true for <![CDATA[
  471. ) {
  472. lineStartOffset--;
  473. }
  474. lineStartOffset = CharArrayUtil.shiftBackward(text, lineStartOffset, "\t ");
  475. if (lineStartOffset < 0) lineStartOffset = 0;
  476. if (lineStartOffset != offset && text.charAt(lineStartOffset) == '\n') {
  477. lineStartOffset++;
  478. }
  479. }
  480. return lineStartOffset;
  481. }
  482. public void adjustTextRange(final FormattingModel model,
  483. final CodeStyleSettings settings,
  484. final CommonCodeStyleSettings.IndentOptions indentOptions,
  485. final TextRange affectedRange,
  486. final boolean keepBlankLines,
  487. final boolean keepLineBreaks,
  488. final boolean changeWSBeforeFirstElement,
  489. final boolean changeLineFeedsBeforeFirstElement,
  490. @Nullable final IndentInfoStorage indentInfoStorage) {
  491. disableFormatting();
  492. try {
  493. final FormatProcessor processor = buildProcessorAndWrapBlocks(
  494. model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, new FormatTextRanges(affectedRange, true)
  495. );
  496. LeafBlockWrapper current = processor.getFirstTokenBlock();
  497. while (current != null) {
  498. WhiteSpace whiteSpace = current.getWhiteSpace();
  499. if (!whiteSpace.isReadOnly()) {
  500. if (whiteSpace.getStartOffset() > affectedRange.getStartOffset()) {
  501. if (whiteSpace.containsLineFeeds() && indentInfoStorage != null) {
  502. whiteSpace.setLineFeedsAreReadOnly(true);
  503. current.setIndentFromParent(indentInfoStorage.getIndentInfo(current.getStartOffset()));
  504. }
  505. else {
  506. whiteSpace.setReadOnly(true);
  507. }
  508. }
  509. else {
  510. if (!changeWSBeforeFirstElement) {
  511. whiteSpace.setReadOnly(true);
  512. }
  513. else {
  514. if (!changeLineFeedsBeforeFirstElement) {
  515. whiteSpace.setLineFeedsAreReadOnly(true);
  516. }
  517. final SpacingImpl spaceProperty = current.getSpaceProperty();
  518. if (spaceProperty != null) {
  519. boolean needChange = false;
  520. int newKeepLineBreaks = spaceProperty.getKeepBlankLines();
  521. boolean newKeepLineBreaksFlag = spaceProperty.shouldKeepLineFeeds();
  522. if (!keepLineBreaks) {
  523. needChange = true;
  524. newKeepLineBreaksFlag = false;
  525. }
  526. if (!keepBlankLines) {
  527. needChange = true;
  528. newKeepLineBreaks = 0;
  529. }
  530. if (needChange) {
  531. assert !(spaceProperty instanceof DependantSpacingImpl);
  532. current.setSpaceProperty(
  533. getSpacingImpl(
  534. spaceProperty.getMinSpaces(), spaceProperty.getMaxSpaces(), spaceProperty.getMinLineFeeds(),
  535. spaceProperty.isReadOnly(),
  536. spaceProperty.isSafe(), newKeepLineBreaksFlag, newKeepLineBreaks, false, spaceProperty.getPrefLineFeeds()
  537. )
  538. );
  539. }
  540. }
  541. }
  542. }
  543. }
  544. current = current.getNextBlock();
  545. }
  546. processor.format(model);
  547. }
  548. finally {
  549. enableFormatting();
  550. }
  551. }
  552. public void adjustTextRange(final FormattingModel model,
  553. final CodeStyleSettings settings,
  554. final CommonCodeStyleSettings.IndentOptions indentOptions,
  555. final TextRange affectedRange) {
  556. disableFormatting();
  557. try {
  558. final FormatProcessor processor = buildProcessorAndWrapBlocks(
  559. model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, new FormatTextRanges(affectedRange, true)
  560. );
  561. LeafBlockWrapper current = processor.getFirstTokenBlock();
  562. while (current != null) {
  563. WhiteSpace whiteSpace = current.getWhiteSpace();
  564. if (!whiteSpace.isReadOnly()) {
  565. if (whiteSpace.getStartOffset() > affectedRange.getStartOffset()) {
  566. whiteSpace.setReadOnly(true);
  567. }
  568. else {
  569. whiteSpace.setReadOnly(false);
  570. }
  571. }
  572. current = current.getNextBlock();
  573. }
  574. processor.format(model);
  575. }
  576. finally {
  577. enableFormatting();
  578. }
  579. }
  580. public void saveIndents(final FormattingModel model, final TextRange affectedRange,
  581. IndentInfoStorage storage,
  582. final CodeStyleSettings settings,
  583. final CommonCodeStyleSettings.IndentOptions indentOptions) {
  584. final Block block = model.getRootBlock();
  585. final FormatProcessor processor = buildProcessorAndWrapBlocks(
  586. model.getDocumentModel(), block, settings, indentOptions, new FormatTextRanges(affectedRange, true)
  587. );
  588. LeafBlockWrapper current = processor.getFirstTokenBlock();
  589. while (current != null) {
  590. WhiteSpace whiteSpace = current.getWhiteSpace();
  591. if (!whiteSpace.isReadOnly() && whiteSpace.containsLineFeeds()) {
  592. storage.saveIndentInfo(current.calcIndentFromParent(), current.getStartOffset());
  593. }
  594. current = current.getNextBlock();
  595. }
  596. }
  597. public FormattingModel createFormattingModelForPsiFile(final PsiFile file,
  598. @NotNull final Block rootBlock,
  599. final CodeStyleSettings settings) {
  600. return new PsiBasedFormattingModel(file, rootBlock, FormattingDocumentModelImpl.createOn(file));
  601. }
  602. public Indent getSpaceIndent(final int spaces, final boolean relative) {
  603. return getIndent(Indent.Type.SPACES, spaces, relative, false);
  604. }
  605. @Override
  606. public Indent getIndent(@NotNull Indent.Type type, boolean relativeToDirectParent, boolean enforceIndentToChildren) {
  607. return getIndent(type, 0, relativeToDirectParent, enforceIndentToChildren);
  608. }
  609. @Override
  610. public Indent getIndent(@NotNull Indent.Type type, int spaces, boolean relativeToDirectParent, boolean enforceIndentToChildren) {
  611. return new IndentImpl(type, false, spaces, relativeToDirectParent, enforceIndentToChildren);
  612. }
  613. public Indent getAbsoluteLabelIndent() {
  614. return myAbsoluteLabelIndent;
  615. }
  616. @NotNull
  617. public Spacing createSafeSpacing(final boolean shouldKeepLineBreaks, final int keepBlankLines) {
  618. return getSpacingImpl(0, 0, 0, false, true, shouldKeepLineBreaks, keepBlankLines, false, 0);
  619. }
  620. @NotNull
  621. public Spacing createKeepingFirstColumnSpacing(final int minSpace,
  622. final int maxSpace,
  623. final boolean keepLineBreaks,
  624. final int keepBlankLines) {
  625. return getSpacingImpl(minSpace, maxSpace, -1, false, false, keepLineBreaks, keepBlankLines, true, 0);
  626. }
  627. @NotNull
  628. public Spacing createSpacing(final int minSpaces, final int maxSpaces, final int minLineFeeds, final boolean keepLineBreaks, final int keepBlankLines,
  629. final int prefLineFeeds) {
  630. return getSpacingImpl(minSpaces, maxSpaces, -1, false, false, keepLineBreaks, keepBlankLines, false, prefLineFeeds);
  631. }
  632. private final Map<SpacingImpl,SpacingImpl> ourSharedProperties = new HashMap<SpacingImpl,SpacingImpl>();
  633. private final SpacingImpl ourSharedSpacing = new SpacingImpl(-1,-1,-1,false,false,false,-1,false,0);
  634. private SpacingImpl getSpacingImpl(final int minSpaces,
  635. final int maxSpaces,
  636. final int minLineFeeds,
  637. final boolean readOnly,
  638. final boolean safe,
  639. final boolean keepLineBreaksFlag,
  640. final int keepLineBreaks,
  641. final boolean keepFirstColumn,
  642. int prefLineFeeds)
  643. {
  644. synchronized(ourSharedSpacing) {
  645. ourSharedSpacing.init(minSpaces, maxSpaces, minLineFeeds, readOnly, safe, keepLineBreaksFlag, keepLineBreaks, keepFirstColumn, prefLineFeeds);
  646. SpacingImpl spacing = ourSharedProperties.get(ourSharedSpacing);
  647. if (spacing == null) {
  648. spacing = new SpacingImpl(minSpaces, maxSpaces, minLineFeeds, readOnly, safe, keepLineBreaksFlag, keepLineBreaks, keepFirstColumn, prefLineFeeds);
  649. ourSharedProperties.put(spacing, spacing);
  650. }
  651. return spacing;
  652. }
  653. }
  654. @NotNull
  655. public String getComponentName() {
  656. return "FormatterEx";
  657. }
  658. public void initComponent() {
  659. }
  660. public void disposeComponent() {
  661. }
  662. public Indent getAbsoluteNoneIndent() {
  663. return myAbsoluteNoneIndent;
  664. }
  665. public Indent getLabelIndent() {
  666. return myLabelIndent;
  667. }
  668. public Indent getContinuationIndent(boolean relative) {
  669. return relative ? myContinuationIndentRelativeToDirectParent : myContinuationIndentNotRelativeToDirectParent;
  670. }
  671. //is default
  672. public Indent getContinuationWithoutFirstIndent(boolean relative) {
  673. return relative ? myContinuationWithoutFirstIndentRelativeToDirectParent : myContinuationWithoutFirstIndentNotRelativeToDirectParent;
  674. }
  675. public boolean isDisabled() {
  676. return myIsDisabledCount.get() > 0;
  677. }
  678. private void disableFormatting() {
  679. myIsDisabledCount.incrementAndGet();
  680. }
  681. private void enableFormatting() {
  682. int old = myIsDisabledCount.getAndDecrement();
  683. if (old <= 0) {
  684. LOG.error("enableFormatting()/disableFormatting() not paired. DisabledLevel = " + old);
  685. }
  686. }
  687. @Nullable
  688. public <T> T runWithFormattingDisabled(@NotNull Computable<T> runnable) {
  689. disableFormatting();
  690. try {
  691. return runnable.compute();
  692. }
  693. finally {
  694. enableFormatting();
  695. }
  696. }
  697. private abstract static class MyFormattingTask implements SequentialTask {
  698. private FormatProcessor myProcessor;
  699. private boolean myDone;
  700. @Override
  701. public void prepare() {
  702. myProcessor = buildProcessor();
  703. }
  704. @Override
  705. public boolean isDone() {
  706. return myDone;
  707. }
  708. @Override
  709. public boolean iteration() {
  710. return myDone = myProcessor.iteration();
  711. }
  712. @Override
  713. public void stop() {
  714. myProcessor.stopSequentialProcessing();
  715. myDone = true;
  716. }
  717. @NotNull
  718. protected abstract FormatProcessor buildProcessor();
  719. }
  720. }