diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/KotlinIndexers.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/KotlinIndexers.kt index 16d46f0f750..6dc663459cd 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/KotlinIndexers.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/KotlinIndexers.kt @@ -86,6 +86,16 @@ class KotlinFilterLexer(private val occurrenceConsumer: OccurrenceConsumer): Bas } else { addOccurrenceInToken(UsageSearchContext.IN_CODE.toInt()) + if (myDelegate.tokenText == "TODO" ) { + // Heuristics to reduce mismatches between indexer and searcher. The searcher returns only occurrences of TO_DO + // as the callee of a call expression, but we can't tell calls and other usages apart based on limited lexer context, + // so we just exclude occurrences in declaration names (and even that doesn't work precisely because it doesn't handle + // declarations with type parameters) + val prevToken = prevTokens.peekFirst() + if (prevToken != KtTokens.FUN_KEYWORD && prevToken != KtTokens.VAR_KEYWORD && prevToken != KtTokens.VAL_KEYWORD && prevToken != KtTokens.CLASS_KEYWORD) { + advanceTodoItemCountsInToken() + } + } } } @@ -125,5 +135,7 @@ class KotlinIdIndexer: LexerBasedIdIndexer() { } class KotlinTodoIndexer: LexerBasedTodoIndexer(), IdAndToDoScannerBasedOnFilterLexer { - override fun createLexer(consumer: OccurrenceConsumer): Lexer = KotlinFilterLexer(consumer) + override fun getVersion() = 2 + + override fun createLexer(consumer: OccurrenceConsumer) = KotlinFilterLexer(consumer) } diff --git a/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java b/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java index 5b16fa73471..63b2a936be4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java +++ b/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathMacros; import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.event.DocumentAdapter; @@ -26,11 +27,13 @@ import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.updateSettings.impl.UpdateChecker; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.search.searches.IndexPatternSearch; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.idea.caches.JarUserDataManager; import org.jetbrains.kotlin.idea.debugger.filter.DebuggerFiltersUtilKt; import org.jetbrains.kotlin.idea.framework.CommonLibraryDetectionUtil; import org.jetbrains.kotlin.idea.framework.KotlinJavaScriptLibraryDetectionUtil; +import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinTodoSearcher; import org.jetbrains.kotlin.utils.PathUtil; import java.io.File; @@ -81,6 +84,8 @@ public class PluginStartupComponent implements ApplicationComponent { } } }); + + ServiceManager.getService(IndexPatternSearch.class).registerExecutor(new KotlinTodoSearcher()); } private static void registerPathVariable() { diff --git a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTodoSearcher.kt b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTodoSearcher.kt new file mode 100644 index 00000000000..ff43a6d4374 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTodoSearcher.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.search.ideaExtensions + +import com.intellij.openapi.application.QueryExecutorBase +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiFile +import com.intellij.psi.impl.cache.TodoCacheManager +import com.intellij.psi.search.IndexPattern +import com.intellij.psi.search.IndexPatternOccurrence +import com.intellij.psi.search.searches.IndexPatternSearch +import com.intellij.util.Processor +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid + +data class KotlinTodoOccurrence(private val _file: PsiFile, private val _textRange: TextRange, private val _pattern: IndexPattern) : IndexPatternOccurrence { + override fun getFile() = _file + override fun getPattern() = _pattern + override fun getTextRange() = _textRange +} + +class KotlinTodoSearcher : QueryExecutorBase(true) { + override fun processQuery(queryParameters: IndexPatternSearch.SearchParameters, consumer: Processor) { + var pattern = queryParameters.pattern + if (pattern != null && !pattern.patternString.contains("TODO", true)) return + if (pattern == null) { + pattern = queryParameters.patternProvider.indexPatterns.firstOrNull { it.patternString.contains("TODO", true) } ?: return + } + + val file = queryParameters.file + + val cacheManager = TodoCacheManager.SERVICE.getInstance(file.project) + val patternProvider = queryParameters.patternProvider + val count = if (patternProvider != null) { + cacheManager.getTodoCount(file.virtualFile, patternProvider)} + else + cacheManager.getTodoCount(file.virtualFile, pattern) + if (count == 0) return + + file.accept(object : KtTreeVisitorVoid() { + override fun visitCallExpression(expression: KtCallExpression) { + if (expression.calleeExpression?.text == "TODO") { + val argList = expression.valueArgumentList + if (argList?.arguments?.size == 1) { + consumer.process(KotlinTodoOccurrence(file, expression.textRange, pattern)) + } + } + } + }) + } +} diff --git a/idea/testData/search/todo/todoCall.kt b/idea/testData/search/todo/todoCall.kt new file mode 100644 index 00000000000..7872ba348e3 --- /dev/null +++ b/idea/testData/search/todo/todoCall.kt @@ -0,0 +1 @@ +fun foo() = TODO("Fix me") diff --git a/idea/testData/search/todo/todoDecl.kt b/idea/testData/search/todo/todoDecl.kt new file mode 100644 index 00000000000..55b48ff873a --- /dev/null +++ b/idea/testData/search/todo/todoDecl.kt @@ -0,0 +1,4 @@ +fun TODO() { +} + +val TODO = 1 diff --git a/idea/tests/org/jetbrains/kotlin/search/TodoSearchTest.kt b/idea/tests/org/jetbrains/kotlin/search/TodoSearchTest.kt new file mode 100644 index 00000000000..3b7235cf4ca --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/search/TodoSearchTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.search + +import com.intellij.psi.search.PsiTodoSearchHelper +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import java.io.File + +class TodoSearchTest : KotlinLightCodeInsightFixtureTestCase() { + override fun getProjectDescriptor(): KotlinLightProjectDescriptor = KotlinLightProjectDescriptor.INSTANCE + + override fun getTestDataPath(): String { + return File(PluginTestCaseBase.getTestDataPathBase(), "/search/todo").path + File.separator + } + + fun testTodoCall() { + val file = myFixture.configureByFile("todoCall.kt") + val todoItems = PsiTodoSearchHelper.SERVICE.getInstance(myFixture.project).findTodoItems(file) + assertEquals(1, todoItems.size) + assertEquals("TODO(\"Fix me\")", todoItems[0].textRange.substring(todoItems[0].file.text)) + } + + fun testTodoDef() { + val file = myFixture.configureByFile("todoDecl.kt") + assertEquals(0, PsiTodoSearchHelper.SERVICE.getInstance(myFixture.project).getTodoItemsCount(file)) + } +}