Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Dart breadcrumb provider #880

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dart/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
<typedHandler implementation="com.jetbrains.lang.dart.ide.editor.DartTypeHandler" id="Dart"/>
<quoteHandler fileType="Dart" className="com.jetbrains.lang.dart.ide.editor.DartQuoteHandler"/>

<breadcrumbsInfoProvider implementation="com.jetbrains.lang.dart.ide.breadcrumbs.DartBreadcrumbsInfoProvider"/>

<lang.commenter language="Dart" implementationClass="com.jetbrains.lang.dart.ide.DartCommenter"/>
<lang.parserDefinition language="Dart" implementationClass="com.jetbrains.lang.dart.DartParserDefinition"/>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.lang.dart.ide.breadcrumbs

import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.util.parents
import com.intellij.ui.breadcrumbs.BreadcrumbsProvider
import com.jetbrains.lang.dart.DartComponentType
import com.jetbrains.lang.dart.DartLanguage
import com.jetbrains.lang.dart.psi.*
import javax.swing.Icon

/**
* Generates breadcrumbs for the selected element in a Dart file.
*/
class DartBreadcrumbsInfoProvider : BreadcrumbsProvider {
override fun getLanguages() = arrayOf(DartLanguage.INSTANCE)

override fun acceptElement(element: PsiElement) = getHandler(element) != null

override fun getElementInfo(element: PsiElement): String =
getHandler(element)!!.getElementInfo(element as DartPsiCompositeElement)

override fun getElementIcon(element: PsiElement): Icon? =
getHandler(element)!!.getElementIcon(element as DartPsiCompositeElement)
?: DartComponentType.typeOf(element)?.icon

/**
* Handles generating crumbs for elements it supports.
*/
private abstract class ElementHandler<TElement : DartPsiCompositeElement>(val type: Class<TElement>) {
/** Whether this handler can generate a crumb for the specified element. */
open fun accepts(element: TElement): Boolean = true

/** The icon to display for some element. */
open fun getElementIcon(element: TElement): Icon? = null

/** A crumb for the specified element. */
abstract fun getElementInfo(element: TElement): String
}

@Suppress("UNCHECKED_CAST")
private fun getHandler(e: PsiElement): ElementHandler<in DartPsiCompositeElement>? {
if (e !is DartPsiCompositeElement) return null
val handler = Handlers.handlers.firstOrNull { it.type.isInstance(e) && (it as ElementHandler<in DartPsiCompositeElement>).accepts(e) }
return handler as ElementHandler<in DartPsiCompositeElement>?
}

/**
* Element handlers to use when generating crumbs.
*/
private object Handlers {
val handlers: List<ElementHandler<*>> = listOf<ElementHandler<*>>(
ConstructorHandler,
NonLocalVariableHandler,
NamedTypeHandler,
UnitTestHandler,
)
}

/**
* Generates crumbs for constructor invocations.
*/
private object ConstructorHandler : ElementHandler<DartCallExpression>(DartCallExpression::class.java) {
override fun accepts(element: DartCallExpression): Boolean {
if (element.expression?.text == null) {
return false
}

val reference = (element.expression as? DartReference)?.resolve()
if (DartComponentType.typeOf(reference) == DartComponentType.CONSTRUCTOR) {
return true
}

return false
}

override fun getElementInfo(element: DartCallExpression): String = element.expression?.text!!
}

/**
* Generates crumbs for non-local variables.
*/
private object NonLocalVariableHandler : ElementHandler<DartVarDeclarationList>(DartVarDeclarationList::class.java) {
override fun accepts(element: DartVarDeclarationList): Boolean = !isInMethodOrFunction(element)

override fun getElementInfo(element: DartVarDeclarationList): String =
element.varAccessDeclaration.componentName.text

override fun getElementIcon(element: DartVarDeclarationList): Icon? = DartComponentType.GLOBAL_VARIABLE.icon
}

/**
* Generates crumbs for named types like classes, enums, mixins, functions, methods, getters, and setters.
*/
private object NamedTypeHandler : ElementHandler<DartComponent>(DartComponent::class.java) {
override fun accepts(element: DartComponent): Boolean =
(
element is DartMethodDeclaration
|| element is DartGetterDeclaration
|| element is DartSetterDeclaration
|| element is DartFunctionDeclarationWithBodyOrNative
|| element is DartClassDefinition
|| element is DartEnumDefinition
|| element is DartEnumConstantDeclaration
|| element is DartMixinDeclaration
) && !isInMethodOrFunction(element)

override fun getElementInfo(element: DartComponent): String {
val name = element.name ?: element.text

if (element is DartSetterDeclaration) return "set $name"
if (element is DartGetterDeclaration) return "get $name"

return name
}
}

/**
* Generates crumbs for `group('...')` and `test('...')` calls in Dart unit tests.
*/
private object UnitTestHandler : ElementHandler<DartCallExpression>(DartCallExpression::class.java) {
override fun accepts(element: DartCallExpression): Boolean {
val name = expressionName(element)
return name == "test" || name == "group"
}

override fun getElementInfo(element: DartCallExpression): String {
val name = expressionName(element)
val label = getTestLabel(element)
return "$name('$label')"
}

private fun expressionName(element: DartCallExpression): String? =
(element.expression as? DartReferenceExpression)?.text

private fun getTestLabel(testCallExpression: DartCallExpression): String? {
val arguments = testCallExpression.arguments
val argumentList = arguments?.argumentList
val argumentExpressions = argumentList?.expressionList

// The first argument to a `group(...)` or `test(...)` call is the label
if (argumentExpressions?.isNotEmpty() == true) {
if (argumentExpressions[0] is DartStringLiteralExpression) {
return StringUtil.unquoteString(argumentExpressions[0].text)
}
}

return null
}
}
}

/**
* Determines whether the element is nested inside a method or function body.
*/
private fun isInMethodOrFunction(element: DartPsiCompositeElement): Boolean {
val parents = element.parents(false).iterator()
while (parents.hasNext()) {
if (parents.next() is DartFunctionBody) {
return true
}
}
return false
}
3 changes: 3 additions & 0 deletions Dart/testData/breadcrumbs/class.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class FooClass {
<caret>
}
3 changes: 3 additions & 0 deletions Dart/testData/breadcrumbs/classVariable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class FooClass {
final bar<caret> = "value";
}
2 changes: 2 additions & 0 deletions Dart/testData/breadcrumbs/classVariable_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FooClass
bar
1 change: 1 addition & 0 deletions Dart/testData/breadcrumbs/class_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FooClass
3 changes: 3 additions & 0 deletions Dart/testData/breadcrumbs/enum.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
enum FooEnum {
E<caret>
}
2 changes: 2 additions & 0 deletions Dart/testData/breadcrumbs/enum_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FooEnum
E
1 change: 1 addition & 0 deletions Dart/testData/breadcrumbs/fileVariable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
final f<caret>oo = "value";
1 change: 1 addition & 0 deletions Dart/testData/breadcrumbs/fileVariable_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo
3 changes: 3 additions & 0 deletions Dart/testData/breadcrumbs/function.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
String funcName() {
return "foo";<caret>
}
1 change: 1 addition & 0 deletions Dart/testData/breadcrumbs/function_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
funcName
5 changes: 5 additions & 0 deletions Dart/testData/breadcrumbs/getter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class FooClass {
String get foo {
return "foo";<caret>
}
}
3 changes: 3 additions & 0 deletions Dart/testData/breadcrumbs/getterFatArrow.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class FooClass {
String get foo => "foo"<caret>;
}
2 changes: 2 additions & 0 deletions Dart/testData/breadcrumbs/getterFatArrow_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FooClass
get foo
2 changes: 2 additions & 0 deletions Dart/testData/breadcrumbs/getter_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FooClass
get foo
5 changes: 5 additions & 0 deletions Dart/testData/breadcrumbs/localVariable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class FooClass {
void methodName() {
final fo<caret>o = "bar";
}
}
2 changes: 2 additions & 0 deletions Dart/testData/breadcrumbs/localVariable_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FooClass
methodName
5 changes: 5 additions & 0 deletions Dart/testData/breadcrumbs/method.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class FooClass {
String methodName() {
return "foo";<caret>
}
}
2 changes: 2 additions & 0 deletions Dart/testData/breadcrumbs/method_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FooClass
methodName
3 changes: 3 additions & 0 deletions Dart/testData/breadcrumbs/mixin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mixin FooMixin {
<caret>
}
1 change: 1 addition & 0 deletions Dart/testData/breadcrumbs/mixin_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FooMixin
5 changes: 5 additions & 0 deletions Dart/testData/breadcrumbs/setter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class FooClass {
set foo(String str) {
<caret>
}
}
3 changes: 3 additions & 0 deletions Dart/testData/breadcrumbs/setterFatArrow.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class FooClass {
set foo(String str) => print('')<caret>;
}
2 changes: 2 additions & 0 deletions Dart/testData/breadcrumbs/setterFatArrow_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FooClass
set foo
2 changes: 2 additions & 0 deletions Dart/testData/breadcrumbs/setter_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FooClass
set foo
7 changes: 7 additions & 0 deletions Dart/testData/breadcrumbs/unitTest.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
void main() {
group('Group', () {
test("Test", () {
<caret>
});
});
}
3 changes: 3 additions & 0 deletions Dart/testData/breadcrumbs/unitTest_crumbs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
main
group('Group')
test('Test')
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.lang.dart.breadcrumbs;

import com.intellij.ui.components.breadcrumbs.Crumb;
import com.jetbrains.lang.dart.DartCodeInsightFixtureTestCase;

import java.util.List;
import java.util.stream.Collectors;

public class DartBreadcrumbsTest extends DartCodeInsightFixtureTestCase {
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/breadcrumbs";
}

private void doTest() {
final String testName = getTestName(true);
myFixture.configureByFile(testName + ".dart");
final String breadcrumbsAndTooltips = getBreadcrumbsAndTooltips(myFixture.getBreadcrumbsAtCaret());
assertSameLinesWithFile(getTestDataPath() + "/" + testName + "_crumbs.txt", breadcrumbsAndTooltips);
}

private static String getBreadcrumbsAndTooltips(List<Crumb> crumbs) {
return crumbs.stream().map(Crumb::getText).collect(Collectors.joining("\n"));
}

public void testClass() {
doTest();
}

public void testClassVariable() {
doTest();
}

public void testEnum() {
doTest();
}

public void testFileVariable() {
doTest();
}

public void testFunction() {
doTest();
}

public void testGetter() {
doTest();
}

public void testGetterFatArrow() {
doTest();
}

public void testLocalVariable() {
doTest();
}

public void testMethod() {
doTest();
}

public void testMixin() {
doTest();
}

public void testSetter() {
doTest();
}

public void testSetterFatArrow() {
doTest();
}

public void testUnitTest() {
doTest();
}
}