commenter/mysql Lexer fix
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / generation / CommentByLineCommentHandler.java
blobde40ccbab40864ae1b60a29f8432a45fdfcdfdf1
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 //myCodeInsightSettings = (CodeInsightSettings)ApplicationManager.getApplication().getComponent(CodeInsightSettings.class);
76 myCodeStyleManager = CodeStyleManager.getInstance(myProject);
78 final SelectionModel selectionModel = editor.getSelectionModel();
80 boolean hasSelection = selectionModel.hasSelection();
81 myStartOffset = selectionModel.getSelectionStart();
82 myEndOffset = selectionModel.getSelectionEnd();
84 if (myDocument.getTextLength() == 0) return;
86 while (true) {
87 int lastLineEnd = myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset));
88 FoldRegion collapsedAt = editor.getFoldingModel().getCollapsedRegionAtOffset(lastLineEnd);
89 if (collapsedAt != null) {
90 final int endOffset = collapsedAt.getEndOffset();
91 if (endOffset <= myEndOffset) {
92 break;
94 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 if (lineStart < myDocument.getTextLength() && myDocument.getCharsSequence().charAt(lineStart) == ' ') lineStart++;
117 editor.getCaretModel().moveToOffset(lineStart);
118 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
121 else {
122 if (!hasSelection) {
123 editor.getCaretModel().moveCaretRelatively(0, 1, false, false, true);
125 else {
126 if (wholeLinesSelected) {
127 selectionModel.setSelection(myStartOffset, selectionModel.getSelectionEnd());
133 private boolean isLineEmpty(final int line) {
134 final CharSequence chars = myDocument.getCharsSequence();
135 int start = myDocument.getLineStartOffset(line);
136 int end = Math.min(myDocument.getLineEndOffset(line), myDocument.getTextLength() - 1);
137 for (int i = start; i <= end; i++) {
138 if (!Character.isWhitespace(chars.charAt(i))) return false;
140 return true;
143 public boolean startInWriteAction() {
144 return true;
147 private void doComment() {
148 myStartLine = myDocument.getLineNumber(myStartOffset);
149 myEndLine = myDocument.getLineNumber(myEndOffset);
151 if (myEndLine > myStartLine && myDocument.getLineStartOffset(myEndLine) == myEndOffset) {
152 myEndLine--;
155 myStartOffsets = new int[myEndLine - myStartLine + 1];
156 myEndOffsets = new int[myEndLine - myStartLine + 1];
157 myCommenters = new Commenter[myEndLine - myStartLine + 1];
158 boolean allLineCommented = true;
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 for (int line = myStartLine; line <= myEndLine; line++) {
197 final Commenter commenter = blockSuitableCommenter != null ? blockSuitableCommenter : findCommenter(line);
198 if (commenter == null) return;
200 if (commenter.getLineCommentPrefix() == null &&
201 (commenter.getBlockCommentPrefix() == null || commenter.getBlockCommentSuffix() == null)) {
202 return;
204 myCommenters[line - myStartLine] = commenter;
205 if (!isLineCommented(line, chars, commenter) && (singleline || !isLineEmpty(line))) {
206 allLineCommented = false;
207 break;
211 if (!allLineCommented) {
212 if (CodeStyleSettingsManager.getSettings(myProject).LINE_COMMENT_AT_FIRST_COLUMN) {
213 doDefaultCommenting(blockSuitableCommenter);
215 else {
216 doIndentCommenting(blockSuitableCommenter);
219 else {
220 for (int line = myEndLine; line >= myStartLine; line--) {
221 uncommentLine(line);
222 //int offset1 = myStartOffsets[line - myStartLine];
223 //int offset2 = myEndOffsets[line - myStartLine];
224 //if (offset1 == offset2) continue;
225 //Commenter commenter = myCommenters[line - myStartLine];
226 //String prefix = commenter.getBlockCommentPrefix();
227 //if (prefix == null || !myDocument.getText().substring(offset1, myDocument.getTextLength()).startsWith(prefix)) {
228 // prefix = commenter.getLineCommentPrefix();
231 //String suffix = commenter.getBlockCommentSuffix();
232 //if (suffix == null && prefix != null) suffix = "";
234 //if (prefix != null && suffix != null) {
235 // final int suffixLen = suffix.length();
236 // final int prefixLen = prefix.length();
237 // if (offset2 >= 0) {
238 // if (!CharArrayUtil.regionMatches(chars, offset1 + prefixLen, prefix)) {
239 // myDocument.deleteString(offset2 - suffixLen, offset2);
240 // }
241 // }
242 // if (offset1 >= 0) {
243 // for (int i = offset2 - suffixLen - 1; i > offset1 + prefixLen; --i) {
244 // if (CharArrayUtil.regionMatches(chars, i, suffix)) {
245 // myDocument.deleteString(i, i + suffixLen);
246 // }
247 // else if (CharArrayUtil.regionMatches(chars, i, prefix)) {
248 // myDocument.deleteString(i, i + prefixLen);
249 // }
250 // }
251 // myDocument.deleteString(offset1, offset1 + prefixLen);
252 // }
258 private boolean isLineCommented(final int line, final CharSequence chars, final Commenter commenter) {
259 String prefix = commenter.getLineCommentPrefix();
260 int lineStart = myDocument.getLineStartOffset(line);
261 lineStart = CharArrayUtil.shiftForward(chars, lineStart, " \t");
262 boolean commented;
263 if (prefix != null) {
264 commented = CharArrayUtil.regionMatches(chars, lineStart, prefix) ||
265 prefix.endsWith(" ") && CharArrayUtil.regionMatches(chars, lineStart, prefix.trim()+"\n");
266 if (commented) {
267 myStartOffsets[line - myStartLine] = lineStart;
268 myEndOffsets[line - myStartLine] = -1;
271 else {
272 prefix = commenter.getBlockCommentPrefix();
273 String suffix = commenter.getBlockCommentSuffix();
274 final int textLength = myDocument.getTextLength();
275 int lineEnd = myDocument.getLineEndOffset(line);
276 if (lineEnd == textLength) {
277 final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t");
278 if (shifted < textLength - 1) lineEnd = shifted;
280 else {
281 lineEnd = CharArrayUtil.shiftBackward(chars, lineEnd, " \t");
283 commented = (lineStart == lineEnd && myStartLine != myEndLine) || (CharArrayUtil.regionMatches(chars, lineStart, prefix) &&
284 CharArrayUtil.regionMatches(chars, lineEnd - suffix.length(), suffix));
285 if (commented) {
286 myStartOffsets[line - myStartLine] = lineStart;
287 myEndOffsets[line - myStartLine] = lineEnd;
290 return commented;
293 @Nullable
294 private Commenter findCommenter(final int line) {
295 final FileType fileType = myFile.getFileType();
296 if (fileType instanceof AbstractFileType) {
297 return ((AbstractFileType)fileType).getCommenter();
300 int lineStartOffset = myDocument.getLineStartOffset(line);
301 int lineEndOffset = myDocument.getLineEndOffset(line) - 1;
302 final CharSequence charSequence = myDocument.getCharsSequence();
303 lineStartOffset = CharArrayUtil.shiftForward(charSequence, lineStartOffset, " \t");
304 lineEndOffset = CharArrayUtil.shiftBackward(charSequence, lineEndOffset < 0 ? 0 : lineEndOffset, " \t");
305 final Language lineStartLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineStartOffset);
306 final Language lineEndLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineEndOffset);
307 return CommentByBlockCommentHandler.getCommenter(myFile, myEditor, lineStartLanguage, lineEndLanguage);
310 private Indent computeMinIndent(int line1, int line2, CharSequence chars, CodeStyleManager codeStyleManager, FileType fileType) {
311 Indent minIndent = CommentUtil.getMinLineIndent(myProject, myDocument, line1, line2, fileType);
312 if (line1 > 0) {
313 int commentOffset = getCommentStart(line1 - 1);
314 if (commentOffset >= 0) {
315 int lineStart = myDocument.getLineStartOffset(line1 - 1);
316 String space = chars.subSequence(lineStart, commentOffset).toString();
317 Indent indent = codeStyleManager.getIndent(space, fileType);
318 minIndent = minIndent != null ? indent.min(minIndent) : indent;
321 if (minIndent == null) {
322 minIndent = codeStyleManager.zeroIndent();
324 return minIndent;
327 private int getCommentStart(int line) {
328 int offset = myDocument.getLineStartOffset(line);
329 CharSequence chars = myDocument.getCharsSequence();
330 offset = CharArrayUtil.shiftForward(chars, offset, " \t");
331 final Commenter commenter = findCommenter(line);
332 if (commenter == null) return -1;
333 String prefix = commenter.getLineCommentPrefix();
334 if (prefix == null) prefix = commenter.getBlockCommentPrefix();
335 if (prefix == null) return -1;
336 return CharArrayUtil.regionMatches(chars, offset, prefix) ? offset : -1;
339 public void doDefaultCommenting(Commenter commenter) {
340 for (int line = myEndLine; line >= myStartLine; line--) {
341 int offset = myDocument.getLineStartOffset(line);
342 commentLine(line, offset, commenter);
346 private void doIndentCommenting(Commenter commenter) {
347 CharSequence chars = myDocument.getCharsSequence();
348 final FileType fileType = myFile.getFileType();
349 Indent minIndent = computeMinIndent(myStartLine, myEndLine, chars, myCodeStyleManager, fileType);
351 for (int line = myEndLine; line >= myStartLine; line--) {
352 int lineStart = myDocument.getLineStartOffset(line);
353 int offset = lineStart;
354 final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
355 try {
356 while (true) {
357 String space = buffer.toString();
358 Indent indent = myCodeStyleManager.getIndent(space, fileType);
359 if (indent.isGreaterThan(minIndent) || indent.equals(minIndent)) break;
360 char c = chars.charAt(offset);
361 if (c != ' ' && c != '\t') {
362 String newSpace = myCodeStyleManager.fillIndent(minIndent, fileType);
363 myDocument.replaceString(lineStart, offset, newSpace);
364 offset = lineStart + newSpace.length();
365 break;
367 buffer.append(c);
368 offset++;
371 finally {
372 StringBuilderSpinAllocator.dispose(buffer);
374 commentLine(line, offset,commenter);
378 private void uncommentRange(int startOffset, int endOffset, @NotNull Commenter commenter) {
379 final String commentedSuffix = commenter.getCommentedBlockCommentSuffix();
380 final String commentedPrefix = commenter.getCommentedBlockCommentPrefix();
381 final String prefix = commenter.getBlockCommentPrefix();
382 final String suffix = commenter.getBlockCommentSuffix();
383 if (prefix == null || suffix == null) {
384 return;
386 if (endOffset >= suffix.length() && CharArrayUtil.regionMatches(myDocument.getCharsSequence(), endOffset - suffix.length(), suffix)) {
387 myDocument.deleteString(endOffset - suffix.length(), endOffset);
389 if (commentedPrefix != null && commentedSuffix != null) {
390 CommentByBlockCommentHandler.commentNestedComments(myDocument, new TextRange(startOffset, endOffset), commenter);
392 myDocument.deleteString(startOffset, startOffset + prefix.length());
395 private void uncommentLine(int line) {
396 Commenter commenter = myCommenters[line - myStartLine];
397 if (commenter == null) commenter = findCommenter(line);
398 if (commenter == null) return;
400 final int startOffset = myStartOffsets[line - myStartLine];
401 final int endOffset = myEndOffsets[line - myStartLine];
402 if (startOffset == endOffset) {
403 return;
405 String prefix = commenter.getLineCommentPrefix();
406 if (prefix != null) {
407 CharSequence chars = myDocument.getCharsSequence();
408 boolean skipNewLine = false;
409 boolean commented = CharArrayUtil.regionMatches(chars, startOffset, prefix) ||
410 (skipNewLine = (prefix.endsWith(" ") && CharArrayUtil.regionMatches(chars, startOffset, prefix.trim() + "\n")));
411 assert commented;
412 int position = 0;//text.indexOf(prefix);
413 myDocument.deleteString(position + startOffset , position + startOffset + (skipNewLine? prefix.trim().length():prefix.length()));
414 return;
416 String text = myDocument.getCharsSequence().subSequence(startOffset, endOffset).toString();
418 prefix = commenter.getBlockCommentPrefix();
419 final String suffix = commenter.getBlockCommentSuffix();
420 if (prefix == null || suffix == null) {
421 return;
424 IntArrayList prefixes = new IntArrayList();
425 IntArrayList suffixes = new IntArrayList();
426 for (int position = 0; position < text.length();) {
427 int prefixPos = text.indexOf(prefix, position);
428 if (prefixPos == -1) {
429 break;
431 prefixes.add(prefixPos);
432 position = prefixPos + prefix.length();
433 int suffixPos = text.indexOf(suffix, position);
434 if (suffixPos == -1) {
435 suffixPos = text.length() - suffix.length();
437 suffixes.add(suffixPos);
438 position = suffixPos + suffix.length();
441 assert prefixes.size() == suffixes.size();
443 for (int i = prefixes.size() - 1; i >= 0; i--) {
444 uncommentRange(startOffset + prefixes.get(i), Math.min(startOffset + suffixes.get(i) + suffix.length(), endOffset), commenter);
448 private void commentLine(int line, int offset, @Nullable Commenter commenter) {
449 if (commenter == null) commenter = findCommenter(line);
450 if (commenter == null) return;
451 String prefix = commenter.getLineCommentPrefix();
452 if (prefix != null) {
453 myDocument.insertString(offset, prefix);
455 else {
456 prefix = commenter.getBlockCommentPrefix();
457 String suffix = commenter.getBlockCommentSuffix();
458 if (prefix == null || suffix == null) return;
459 int endOffset = myDocument.getLineEndOffset(line);
460 if (endOffset == offset && myStartLine != myEndLine) return;
461 final int textLength = myDocument.getTextLength();
462 final CharSequence chars = myDocument.getCharsSequence();
463 offset = CharArrayUtil.shiftForward(chars, offset, " \t");
464 if (endOffset == textLength) {
465 final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t");
466 if (shifted < textLength - 1) endOffset = shifted;
468 else {
469 endOffset = CharArrayUtil.shiftBackward(chars, endOffset, " \t");
471 final String text = myDocument.getCharsSequence().subSequence(offset, endOffset).toString();
472 final IntArrayList prefixes = new IntArrayList();
473 final IntArrayList suffixes = new IntArrayList();
474 final String commentedSuffix = commenter.getCommentedBlockCommentSuffix();
475 final String commentedPrefix = commenter.getCommentedBlockCommentPrefix();
476 for (int position = 0; position < text.length();) {
477 int nearestPrefix = text.indexOf(prefix, position);
478 if (nearestPrefix == -1) {
479 nearestPrefix = text.length();
481 int nearestSuffix = text.indexOf(suffix, position);
482 if (nearestSuffix == -1) {
483 nearestSuffix = text.length();
485 if (Math.min(nearestPrefix, nearestSuffix) == text.length()) {
486 break;
488 if (nearestPrefix < nearestSuffix) {
489 prefixes.add(nearestPrefix);
490 position = nearestPrefix + prefix.length();
492 else {
493 suffixes.add(nearestSuffix);
494 position = nearestSuffix + suffix.length();
497 if (!(commentedSuffix == null && !suffixes.isEmpty() && offset + suffixes.get(suffixes.size() - 1) + suffix.length() >= endOffset)) {
498 myDocument.insertString(endOffset, suffix);
500 int nearestPrefix = prefixes.size() - 1;
501 int nearestSuffix = suffixes.size() - 1;
502 while (nearestPrefix >= 0 || nearestSuffix >= 0) {
503 if (nearestSuffix == -1 || nearestPrefix != -1 && prefixes.get(nearestPrefix) > suffixes.get(nearestSuffix)) {
504 final int position = prefixes.get(nearestPrefix);
505 nearestPrefix--;
506 if (commentedPrefix != null) {
507 myDocument.replaceString(offset + position, offset + position + prefix.length(), commentedPrefix);
509 else if (position != 0) {
510 myDocument.insertString(offset + position, suffix);
513 else {
514 final int position = suffixes.get(nearestSuffix);
515 nearestSuffix--;
516 if (commentedSuffix != null) {
517 myDocument.replaceString(offset + position, offset + position + suffix.length(), commentedSuffix);
519 else if (offset + position + suffix.length() < endOffset) {
520 myDocument.insertString(offset + position + suffix.length(), prefix);
524 if (!(commentedPrefix == null && !prefixes.isEmpty() && prefixes.get(0) == 0)) {
525 myDocument.insertString(offset, prefix);