ComponentWithBrowseButton - optional remove listener on hide
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / generation / CommentByLineCommentHandler.java
blob56cc5f4bdb63fdfd0549f3b36f226ea527ef337d
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInsight.generation;
19 import com.intellij.codeInsight.CodeInsightActionHandler;
20 import com.intellij.codeInsight.CommentUtil;
21 import com.intellij.featureStatistics.FeatureUsageTracker;
22 import com.intellij.ide.highlighter.custom.SyntaxTable;
23 import com.intellij.lang.Commenter;
24 import com.intellij.lang.Language;
25 import com.intellij.lang.LanguageCommenters;
26 import com.intellij.openapi.actionSystem.IdeActions;
27 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
28 import com.intellij.openapi.editor.*;
29 import com.intellij.openapi.fileEditor.FileDocumentManager;
30 import com.intellij.openapi.fileTypes.FileType;
31 import com.intellij.openapi.fileTypes.impl.AbstractFileType;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.util.Comparing;
34 import com.intellij.openapi.util.TextRange;
35 import com.intellij.psi.PsiDocumentManager;
36 import com.intellij.psi.PsiFile;
37 import com.intellij.psi.codeStyle.CodeStyleManager;
38 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
39 import com.intellij.psi.codeStyle.Indent;
40 import com.intellij.psi.util.PsiUtilBase;
41 import com.intellij.util.StringBuilderSpinAllocator;
42 import com.intellij.util.containers.IntArrayList;
43 import com.intellij.util.text.CharArrayUtil;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
47 public class CommentByLineCommentHandler implements CodeInsightActionHandler {
48 private Project myProject;
49 private PsiFile myFile;
50 private Document myDocument;
51 private Editor myEditor;
52 private int myStartOffset;
53 private int myEndOffset;
54 private int myStartLine;
55 private int myEndLine;
56 private int[] myStartOffsets;
57 private int[] myEndOffsets;
58 private Commenter[] myCommenters;
59 private CodeStyleManager myCodeStyleManager;
61 public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
62 myProject = project;
63 myFile = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage());
64 myDocument = editor.getDocument();
65 myEditor = editor;
67 if (!FileDocumentManager.getInstance().requestWriting(myDocument, project)) {
68 return;
71 PsiDocumentManager.getInstance(project).commitDocument(myDocument);
73 FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.line");
75 myCodeStyleManager = CodeStyleManager.getInstance(myProject);
77 final SelectionModel selectionModel = editor.getSelectionModel();
79 boolean hasSelection = selectionModel.hasSelection();
80 myStartOffset = selectionModel.getSelectionStart();
81 myEndOffset = selectionModel.getSelectionEnd();
83 if (myDocument.getTextLength() == 0) return;
85 while (true) {
86 int lastLineEnd = myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset));
87 FoldRegion collapsedAt = editor.getFoldingModel().getCollapsedRegionAtOffset(lastLineEnd);
88 if (collapsedAt != null) {
89 final int endOffset = collapsedAt.getEndOffset();
90 if (endOffset <= myEndOffset) {
91 break;
93 myEndOffset = endOffset;
95 else {
96 break;
100 boolean wholeLinesSelected = !hasSelection ||
101 myStartOffset == myDocument.getLineStartOffset(myDocument.getLineNumber(myStartOffset)) &&
102 myEndOffset == myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset - 1)) + 1;
104 boolean startingNewLineComment = !hasSelection && isLineEmpty(myDocument.getLineNumber(myStartOffset)) && !Comparing
105 .equal(IdeActions.ACTION_COMMENT_LINE, ActionManagerEx.getInstanceEx().getPrevPreformedActionId());
106 doComment();
108 if (startingNewLineComment) {
109 final Commenter commenter = myCommenters[0];
110 if (commenter != null) {
111 String prefix = commenter.getLineCommentPrefix();
112 if (prefix == null) prefix = commenter.getBlockCommentPrefix();
113 int lineStart = myDocument.getLineStartOffset(myStartLine);
114 lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t");
115 lineStart += prefix.length();
116 lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t");
117 if (lineStart > myDocument.getTextLength()) lineStart = myDocument.getTextLength();
118 editor.getCaretModel().moveToOffset(lineStart);
119 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
122 else {
123 if (!hasSelection) {
124 editor.getCaretModel().moveCaretRelatively(0, 1, false, false, true);
126 else {
127 if (wholeLinesSelected) {
128 selectionModel.setSelection(myStartOffset, selectionModel.getSelectionEnd());
134 private boolean isLineEmpty(final int line) {
135 final CharSequence chars = myDocument.getCharsSequence();
136 int start = myDocument.getLineStartOffset(line);
137 int end = Math.min(myDocument.getLineEndOffset(line), myDocument.getTextLength() - 1);
138 for (int i = start; i <= end; i++) {
139 if (!Character.isWhitespace(chars.charAt(i))) return false;
141 return true;
144 public boolean startInWriteAction() {
145 return true;
148 private void doComment() {
149 myStartLine = myDocument.getLineNumber(myStartOffset);
150 myEndLine = myDocument.getLineNumber(myEndOffset);
152 if (myEndLine > myStartLine && myDocument.getLineStartOffset(myEndLine) == myEndOffset) {
153 myEndLine--;
156 myStartOffsets = new int[myEndLine - myStartLine + 1];
157 myEndOffsets = new int[myEndLine - myStartLine + 1];
158 myCommenters = new Commenter[myEndLine - myStartLine + 1];
159 CharSequence chars = myDocument.getCharsSequence();
161 boolean singleline = myStartLine == myEndLine;
162 int offset = myDocument.getLineStartOffset(myStartLine);
163 offset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t");
164 final Language languageSuitableForCompleteFragment = PsiUtilBase.reallyEvaluateLanguageInRange(offset, CharArrayUtil.shiftBackward(
165 myDocument.getCharsSequence(), myDocument.getLineEndOffset(myEndLine), " \t\n"), myFile);
167 Commenter blockSuitableCommenter = languageSuitableForCompleteFragment == null ? LanguageCommenters.INSTANCE.forLanguage(myFile.getLanguage()) : null;
168 if (blockSuitableCommenter == null && myFile.getFileType() instanceof AbstractFileType) {
169 blockSuitableCommenter = new Commenter() {
170 final SyntaxTable mySyntaxTable = ((AbstractFileType)myFile.getFileType()).getSyntaxTable();
171 @Nullable
172 public String getLineCommentPrefix() {
173 return mySyntaxTable.getLineComment();
176 @Nullable
177 public String getBlockCommentPrefix() {
178 return mySyntaxTable.getStartComment();
181 @Nullable
182 public String getBlockCommentSuffix() {
183 return mySyntaxTable.getEndComment();
186 public String getCommentedBlockCommentPrefix() {
187 return null;
190 public String getCommentedBlockCommentSuffix() {
191 return null;
196 boolean allLineCommented = true;
197 for (int line = myStartLine; line <= myEndLine; line++) {
198 final Commenter commenter = blockSuitableCommenter != null ? blockSuitableCommenter : findCommenter(line);
199 if (commenter == null) return;
201 if (commenter.getLineCommentPrefix() == null &&
202 (commenter.getBlockCommentPrefix() == null || commenter.getBlockCommentSuffix() == null)) {
203 return;
205 myCommenters[line - myStartLine] = commenter;
206 if (!isLineCommented(line, chars, commenter) && (singleline || !isLineEmpty(line))) {
207 allLineCommented = false;
208 break;
212 if (!allLineCommented) {
213 if (CodeStyleSettingsManager.getSettings(myProject).LINE_COMMENT_AT_FIRST_COLUMN) {
214 doDefaultCommenting(blockSuitableCommenter);
216 else {
217 doIndentCommenting(blockSuitableCommenter);
220 else {
221 for (int line = myEndLine; line >= myStartLine; line--) {
222 uncommentLine(line);
223 //int offset1 = myStartOffsets[line - myStartLine];
224 //int offset2 = myEndOffsets[line - myStartLine];
225 //if (offset1 == offset2) continue;
226 //Commenter commenter = myCommenters[line - myStartLine];
227 //String prefix = commenter.getBlockCommentPrefix();
228 //if (prefix == null || !myDocument.getText().substring(offset1, myDocument.getTextLength()).startsWith(prefix)) {
229 // prefix = commenter.getLineCommentPrefix();
232 //String suffix = commenter.getBlockCommentSuffix();
233 //if (suffix == null && prefix != null) suffix = "";
235 //if (prefix != null && suffix != null) {
236 // final int suffixLen = suffix.length();
237 // final int prefixLen = prefix.length();
238 // if (offset2 >= 0) {
239 // if (!CharArrayUtil.regionMatches(chars, offset1 + prefixLen, prefix)) {
240 // myDocument.deleteString(offset2 - suffixLen, offset2);
241 // }
242 // }
243 // if (offset1 >= 0) {
244 // for (int i = offset2 - suffixLen - 1; i > offset1 + prefixLen; --i) {
245 // if (CharArrayUtil.regionMatches(chars, i, suffix)) {
246 // myDocument.deleteString(i, i + suffixLen);
247 // }
248 // else if (CharArrayUtil.regionMatches(chars, i, prefix)) {
249 // myDocument.deleteString(i, i + prefixLen);
250 // }
251 // }
252 // myDocument.deleteString(offset1, offset1 + prefixLen);
253 // }
259 private boolean isLineCommented(final int line, final CharSequence chars, final Commenter commenter) {
260 String prefix = commenter.getLineCommentPrefix();
261 int lineStart = myDocument.getLineStartOffset(line);
262 lineStart = CharArrayUtil.shiftForward(chars, lineStart, " \t");
263 boolean commented;
264 if (prefix != null) {
265 commented = CharArrayUtil.regionMatches(chars, lineStart, prefix) ||
266 prefix.endsWith(" ") && CharArrayUtil.regionMatches(chars, lineStart, prefix.trim()+"\n");
267 if (commented) {
268 myStartOffsets[line - myStartLine] = lineStart;
269 myEndOffsets[line - myStartLine] = -1;
272 else {
273 prefix = commenter.getBlockCommentPrefix();
274 String suffix = commenter.getBlockCommentSuffix();
275 final int textLength = myDocument.getTextLength();
276 int lineEnd = myDocument.getLineEndOffset(line);
277 if (lineEnd == textLength) {
278 final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t");
279 if (shifted < textLength - 1) lineEnd = shifted;
281 else {
282 lineEnd = CharArrayUtil.shiftBackward(chars, lineEnd, " \t");
284 commented = lineStart == lineEnd && myStartLine != myEndLine ||
285 CharArrayUtil.regionMatches(chars, lineStart, prefix)
286 && CharArrayUtil.regionMatches(chars, lineEnd - suffix.length(), suffix);
287 if (commented) {
288 myStartOffsets[line - myStartLine] = lineStart;
289 myEndOffsets[line - myStartLine] = lineEnd;
292 return commented;
295 @Nullable
296 private Commenter findCommenter(final int line) {
297 final FileType fileType = myFile.getFileType();
298 if (fileType instanceof AbstractFileType) {
299 return ((AbstractFileType)fileType).getCommenter();
302 int lineStartOffset = myDocument.getLineStartOffset(line);
303 int lineEndOffset = myDocument.getLineEndOffset(line) - 1;
304 final CharSequence charSequence = myDocument.getCharsSequence();
305 lineStartOffset = CharArrayUtil.shiftForward(charSequence, lineStartOffset, " \t");
306 lineEndOffset = CharArrayUtil.shiftBackward(charSequence, lineEndOffset < 0 ? 0 : lineEndOffset, " \t");
307 final Language lineStartLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineStartOffset);
308 final Language lineEndLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineEndOffset);
309 return CommentByBlockCommentHandler.getCommenter(myFile, myEditor, lineStartLanguage, lineEndLanguage);
312 private Indent computeMinIndent(int line1, int line2, CharSequence chars, CodeStyleManager codeStyleManager, FileType fileType) {
313 Indent minIndent = CommentUtil.getMinLineIndent(myProject, myDocument, line1, line2, fileType);
314 if (line1 > 0) {
315 int commentOffset = getCommentStart(line1 - 1);
316 if (commentOffset >= 0) {
317 int lineStart = myDocument.getLineStartOffset(line1 - 1);
318 String space = chars.subSequence(lineStart, commentOffset).toString();
319 Indent indent = codeStyleManager.getIndent(space, fileType);
320 minIndent = minIndent != null ? indent.min(minIndent) : indent;
323 if (minIndent == null) {
324 minIndent = codeStyleManager.zeroIndent();
326 return minIndent;
329 private int getCommentStart(int line) {
330 int offset = myDocument.getLineStartOffset(line);
331 CharSequence chars = myDocument.getCharsSequence();
332 offset = CharArrayUtil.shiftForward(chars, offset, " \t");
333 final Commenter commenter = findCommenter(line);
334 if (commenter == null) return -1;
335 String prefix = commenter.getLineCommentPrefix();
336 if (prefix == null) prefix = commenter.getBlockCommentPrefix();
337 if (prefix == null) return -1;
338 return CharArrayUtil.regionMatches(chars, offset, prefix) ? offset : -1;
341 public void doDefaultCommenting(Commenter commenter) {
342 for (int line = myEndLine; line >= myStartLine; line--) {
343 int offset = myDocument.getLineStartOffset(line);
344 commentLine(line, offset, commenter);
348 private void doIndentCommenting(Commenter commenter) {
349 CharSequence chars = myDocument.getCharsSequence();
350 final FileType fileType = myFile.getFileType();
351 Indent minIndent = computeMinIndent(myStartLine, myEndLine, chars, myCodeStyleManager, fileType);
353 for (int line = myEndLine; line >= myStartLine; line--) {
354 int lineStart = myDocument.getLineStartOffset(line);
355 int offset = lineStart;
356 final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
357 try {
358 while (true) {
359 String space = buffer.toString();
360 Indent indent = myCodeStyleManager.getIndent(space, fileType);
361 if (indent.isGreaterThan(minIndent) || indent.equals(minIndent)) break;
362 char c = chars.charAt(offset);
363 if (c != ' ' && c != '\t') {
364 String newSpace = myCodeStyleManager.fillIndent(minIndent, fileType);
365 myDocument.replaceString(lineStart, offset, newSpace);
366 offset = lineStart + newSpace.length();
367 break;
369 buffer.append(c);
370 offset++;
373 finally {
374 StringBuilderSpinAllocator.dispose(buffer);
376 commentLine(line, offset,commenter);
380 private void uncommentRange(int startOffset, int endOffset, @NotNull Commenter commenter) {
381 final String commentedSuffix = commenter.getCommentedBlockCommentSuffix();
382 final String commentedPrefix = commenter.getCommentedBlockCommentPrefix();
383 final String prefix = commenter.getBlockCommentPrefix();
384 final String suffix = commenter.getBlockCommentSuffix();
385 if (prefix == null || suffix == null) {
386 return;
388 if (endOffset >= suffix.length() && CharArrayUtil.regionMatches(myDocument.getCharsSequence(), endOffset - suffix.length(), suffix)) {
389 myDocument.deleteString(endOffset - suffix.length(), endOffset);
391 if (commentedPrefix != null && commentedSuffix != null) {
392 CommentByBlockCommentHandler.commentNestedComments(myDocument, new TextRange(startOffset, endOffset), commenter);
394 myDocument.deleteString(startOffset, startOffset + prefix.length());
397 private void uncommentLine(int line) {
398 Commenter commenter = myCommenters[line - myStartLine];
399 if (commenter == null) commenter = findCommenter(line);
400 if (commenter == null) return;
402 final int startOffset = myStartOffsets[line - myStartLine];
403 final int endOffset = myEndOffsets[line - myStartLine];
404 if (startOffset == endOffset) {
405 return;
407 String prefix = commenter.getLineCommentPrefix();
408 if (prefix != null) {
409 CharSequence chars = myDocument.getCharsSequence();
410 boolean skipNewLine = false;
411 boolean commented = CharArrayUtil.regionMatches(chars, startOffset, prefix) ||
412 (skipNewLine = prefix.endsWith(" ") && CharArrayUtil.regionMatches(chars, startOffset, prefix.trim() + "\n"));
413 assert commented;
414 int position = 0;//text.indexOf(prefix);
415 myDocument.deleteString(position + startOffset , position + startOffset + (skipNewLine? prefix.trim().length():prefix.length()));
416 return;
418 String text = myDocument.getCharsSequence().subSequence(startOffset, endOffset).toString();
420 prefix = commenter.getBlockCommentPrefix();
421 final String suffix = commenter.getBlockCommentSuffix();
422 if (prefix == null || suffix == null) {
423 return;
426 IntArrayList prefixes = new IntArrayList();
427 IntArrayList suffixes = new IntArrayList();
428 for (int position = 0; position < text.length();) {
429 int prefixPos = text.indexOf(prefix, position);
430 if (prefixPos == -1) {
431 break;
433 prefixes.add(prefixPos);
434 position = prefixPos + prefix.length();
435 int suffixPos = text.indexOf(suffix, position);
436 if (suffixPos == -1) {
437 suffixPos = text.length() - suffix.length();
439 suffixes.add(suffixPos);
440 position = suffixPos + suffix.length();
443 assert prefixes.size() == suffixes.size();
445 for (int i = prefixes.size() - 1; i >= 0; i--) {
446 uncommentRange(startOffset + prefixes.get(i), Math.min(startOffset + suffixes.get(i) + suffix.length(), endOffset), commenter);
450 private void commentLine(int line, int offset, @Nullable Commenter commenter) {
451 if (commenter == null) commenter = findCommenter(line);
452 if (commenter == null) return;
453 String prefix = commenter.getLineCommentPrefix();
454 if (prefix != null) {
455 myDocument.insertString(offset, prefix);
457 else {
458 prefix = commenter.getBlockCommentPrefix();
459 String suffix = commenter.getBlockCommentSuffix();
460 if (prefix == null || suffix == null) return;
461 int endOffset = myDocument.getLineEndOffset(line);
462 if (endOffset == offset && myStartLine != myEndLine) return;
463 final int textLength = myDocument.getTextLength();
464 final CharSequence chars = myDocument.getCharsSequence();
465 offset = CharArrayUtil.shiftForward(chars, offset, " \t");
466 if (endOffset == textLength) {
467 final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t");
468 if (shifted < textLength - 1) endOffset = shifted;
470 else {
471 endOffset = CharArrayUtil.shiftBackward(chars, endOffset, " \t");
473 if (endOffset <= offset) return;
474 final String text = chars.subSequence(offset, endOffset).toString();
475 final IntArrayList prefixes = new IntArrayList();
476 final IntArrayList suffixes = new IntArrayList();
477 final String commentedSuffix = commenter.getCommentedBlockCommentSuffix();
478 final String commentedPrefix = commenter.getCommentedBlockCommentPrefix();
479 for (int position = 0; position < text.length();) {
480 int nearestPrefix = text.indexOf(prefix, position);
481 if (nearestPrefix == -1) {
482 nearestPrefix = text.length();
484 int nearestSuffix = text.indexOf(suffix, position);
485 if (nearestSuffix == -1) {
486 nearestSuffix = text.length();
488 if (Math.min(nearestPrefix, nearestSuffix) == text.length()) {
489 break;
491 if (nearestPrefix < nearestSuffix) {
492 prefixes.add(nearestPrefix);
493 position = nearestPrefix + prefix.length();
495 else {
496 suffixes.add(nearestSuffix);
497 position = nearestSuffix + suffix.length();
500 if (!(commentedSuffix == null && !suffixes.isEmpty() && offset + suffixes.get(suffixes.size() - 1) + suffix.length() >= endOffset)) {
501 myDocument.insertString(endOffset, suffix);
503 int nearestPrefix = prefixes.size() - 1;
504 int nearestSuffix = suffixes.size() - 1;
505 while (nearestPrefix >= 0 || nearestSuffix >= 0) {
506 if (nearestSuffix == -1 || nearestPrefix != -1 && prefixes.get(nearestPrefix) > suffixes.get(nearestSuffix)) {
507 final int position = prefixes.get(nearestPrefix);
508 nearestPrefix--;
509 if (commentedPrefix != null) {
510 myDocument.replaceString(offset + position, offset + position + prefix.length(), commentedPrefix);
512 else if (position != 0) {
513 myDocument.insertString(offset + position, suffix);
516 else {
517 final int position = suffixes.get(nearestSuffix);
518 nearestSuffix--;
519 if (commentedSuffix != null) {
520 myDocument.replaceString(offset + position, offset + position + suffix.length(), commentedSuffix);
522 else if (offset + position + suffix.length() < endOffset) {
523 myDocument.insertString(offset + position + suffix.length(), prefix);
527 if (!(commentedPrefix == null && !prefixes.isEmpty() && prefixes.get(0) == 0)) {
528 myDocument.insertString(offset, prefix);