Search suggestions by static attribute (#6036)

close #5874

Changelog:
- add: `isStatic` parameter to `search/completion` request to search by the `static` suggestion attribute
- update: search non-static suggestions when opening component browser

# Important Notes
Component browser doesn't show `Table.new` and `Table.from_rows` suggestions when a `Table` node is selected.

![2023-03-21-151117_1301x877_scrot](https://user-images.githubusercontent.com/357683/226874291-1ff99994-1bb6-41df-96b4-dc5c5178ba41.png)
This commit is contained in:
Dmitry Bushev 2023-03-23 18:02:25 +03:00 committed by GitHub
parent 0bdf44cba8
commit 4c62dc9061
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 200 additions and 39 deletions

View File

@ -177,6 +177,7 @@ trait API {
, self_type : Option<String>
, return_type : Option<String>
, tags : Option<Vec<SuggestionEntryType>>
, is_static : Option<bool>
) -> response::Completion;
/// Get the list of component groups available in runtime.

View File

@ -972,9 +972,11 @@ impl Searcher {
let this = self.clone_ref();
executor::global::spawn(async move {
let this_type = this_type.await;
let is_static = this_type.is_some().then_some(false);
info!("Requesting new suggestion list. Type of `self` is {this_type:?}.");
let file = graph.module.path().file_path();
let response = ls.completion(file, &position, &this_type, &None, &tags).await;
let response =
ls.completion(file, &position, &this_type, &None, &tags, &is_static).await;
match response {
Ok(response) => {
info!("Received suggestions from Language Server.");
@ -1359,7 +1361,7 @@ pub mod test {
pub fn expect_completion(client: &mut language_server::MockClient, results: &[SuggestionId]) {
let response = completion_response(results);
client.expect.completion(|_, _, _, _, _| Ok(response))
client.expect.completion(|_, _, _, _, _, _| Ok(response))
}
#[derive(Debug, Derivative)]
@ -1389,12 +1391,14 @@ pub mod test {
result: &[SuggestionId],
) {
let completion_response = completion_response(result);
let is_static = self_type.is_some().then_some(false);
expect_call!(client.completion(
module = self.graph.module.path.file_path().clone(),
position = self.code_location,
self_type = self_type.map(Into::into),
return_type = None,
tag = None
tag = None,
is_static = is_static
) => Ok(completion_response));
}
}

View File

@ -4368,6 +4368,8 @@ Sent from client to the server to receive the autocomplete suggestion.
returnType?: string;
// Filter by the suggestion types
tags?: [SuggestionEntryType];
// Filter by `static` attribute of the suggestion
isStatic?: Boolean;
}
```

View File

@ -33,14 +33,15 @@ class CompletionHandler(
case Request(
Completion,
id,
Completion.Params(file, pos, selfType, returnType, tags)
Completion.Params(file, pos, selfType, returnType, tags, isStatic)
) =>
suggestionsHandler ! SearchProtocol.Completion(
file,
pos,
selfType,
returnType,
tags
tags,
isStatic
)
val cancellable =
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)

View File

@ -76,7 +76,8 @@ object SearchApi {
position: Position,
selfType: Option[String],
returnType: Option[String],
tags: Option[Seq[SuggestionKind]]
tags: Option[Seq[SuggestionKind]],
isStatic: Option[Boolean]
)
case class Result(results: Seq[SuggestionId], currentVersion: Long)

View File

@ -504,13 +504,15 @@ object SearchProtocol {
* @param selfType filter entries matching the self type
* @param returnType filter entries matching the return type
* @param tags filter entries by suggestion type
* @param isStatic filter entries by `static` field
*/
case class Completion(
file: Path,
position: Position,
selfType: Option[String],
returnType: Option[String],
tags: Option[Seq[SuggestionKind]]
tags: Option[Seq[SuggestionKind]],
isStatic: Option[Boolean]
)
/** The reply to the [[Completion]] request.

View File

@ -373,7 +373,7 @@ final class SuggestionsHandler(
}
.pipeTo(sender())
case Completion(path, pos, selfType, returnType, tags) =>
case Completion(path, pos, selfType, returnType, tags, isStatic) =>
val selfTypes = selfType.toList.flatMap(ty => ty :: graph.getParents(ty))
getModuleName(projectName, path)
.flatMap { either =>
@ -386,7 +386,8 @@ final class SuggestionsHandler(
selfTypes,
returnType,
tags.map(_.map(SuggestionKind.toSuggestion)),
Some(toPosition(pos))
Some(toPosition(pos)),
isStatic
)
.map(CompletionResult.tupled)
)

View File

@ -837,7 +837,8 @@ class SuggestionsHandlerSpec
position = Position(0, 0),
selfType = None,
returnType = None,
tags = None
tags = None,
isStatic = None
)
expectMsg(
@ -865,7 +866,8 @@ class SuggestionsHandlerSpec
position = Position(0, 0),
selfType = Some("MyType"),
returnType = None,
tags = None
tags = None,
isStatic = None
)
expectMsg(
@ -889,7 +891,8 @@ class SuggestionsHandlerSpec
position = Position(0, 0),
selfType = Some("Integer"),
returnType = None,
tags = None
tags = None,
isStatic = None
)
expectMsg(
@ -910,7 +913,8 @@ class SuggestionsHandlerSpec
position = Position(0, 0),
selfType = Some("Any"),
returnType = None,
tags = None
tags = None,
isStatic = None
)
expectMsg(
@ -930,7 +934,8 @@ class SuggestionsHandlerSpec
position = Position(1, 10),
selfType = None,
returnType = Some("IO"),
tags = None
tags = None,
isStatic = None
)
expectMsg(
@ -950,7 +955,8 @@ class SuggestionsHandlerSpec
position = Position(42, 0),
selfType = None,
returnType = None,
tags = Some(Seq(SearchProtocol.SuggestionKind.Local))
tags = Some(Seq(SearchProtocol.SuggestionKind.Local)),
isStatic = None
)
expectMsg(
@ -960,6 +966,39 @@ class SuggestionsHandlerSpec
)
)
}
"search entries by static attribute" taggedAs Retry in withDb {
(config, repo, _, _, handler) =>
val all = Seq(
Suggestions.module,
Suggestions.tpe,
Suggestions.constructor,
Suggestions.method.copy(isStatic = true),
Suggestions.function,
Suggestions.local,
Suggestions.methodOnAny,
Suggestions.methodOnNumber,
Suggestions.methodOnInteger
)
val (_, Seq(_, _, _, methodId, _, _, _, _, _)) =
Await.result(repo.insertAll(all), Timeout)
handler ! SearchProtocol.Completion(
file = mkModulePath(config, "Main.enso"),
position = Position(42, 0),
selfType = None,
returnType = None,
tags = None,
isStatic = Some(true)
)
expectMsg(
SearchProtocol.CompletionResult(
all.length.toLong,
Seq(methodId).flatten
)
)
}
}
private def fieldUpdate(value: String): SearchProtocol.FieldUpdate[String] =

View File

@ -79,6 +79,7 @@ public class SuggestionsRepoBenchmark {
CollectionConverters.ListHasAsScala(new ArrayList<String>()).asScala().toSeq(),
none(),
none(),
none(),
none()),
TIMEOUT);
}
@ -91,6 +92,7 @@ public class SuggestionsRepoBenchmark {
CollectionConverters.ListHasAsScala(new ArrayList<String>()).asScala().toSeq(),
scala.Some.apply("MyType"),
none(),
none(),
none()),
TIMEOUT);
}
@ -105,6 +107,7 @@ public class SuggestionsRepoBenchmark {
CollectionConverters.ListHasAsScala(selfTypes).asScala().toSeq(),
none(),
none(),
none(),
none()),
TIMEOUT);
}
@ -119,6 +122,7 @@ public class SuggestionsRepoBenchmark {
CollectionConverters.ListHasAsScala(selfTypes).asScala().toSeq(),
scala.Some.apply("ReturnType"),
none(),
none(),
none()),
TIMEOUT);
}
@ -133,7 +137,8 @@ public class SuggestionsRepoBenchmark {
CollectionConverters.ListHasAsScala(selfTypes).asScala().toSeq(),
scala.Some.apply("ReturnType"),
scala.Some.apply(kinds),
none()),
none(),
scala.Some.apply(false)),
TIMEOUT);
}

View File

@ -45,6 +45,7 @@ trait SuggestionsRepo[F[_]] {
* @param returnType the returnType search parameter
* @param kinds the list suggestion kinds to search
* @param position the absolute position in the text
* @param isStatic the static attribute
* @return the current database version and the list of found suggestion ids,
* ranked by specificity
*/
@ -53,7 +54,8 @@ trait SuggestionsRepo[F[_]] {
selfType: Seq[String],
returnType: Option[String],
kinds: Option[Seq[Suggestion.Kind]],
position: Option[Suggestion.Position]
position: Option[Suggestion.Position],
isStatic: Option[Boolean]
): F[(Long, Seq[Long])]
/** Select the suggestion by id.

View File

@ -65,9 +65,10 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
selfType: Seq[String],
returnType: Option[String],
kinds: Option[Seq[Suggestion.Kind]],
position: Option[Suggestion.Position]
position: Option[Suggestion.Position],
isStatic: Option[Boolean]
): Future[(Long, Seq[Long])] =
db.run(searchQuery(module, selfType, returnType, kinds, position))
db.run(searchQuery(module, selfType, returnType, kinds, position, isStatic))
/** @inheritdoc */
override def select(id: Long): Future[Option[Suggestion]] =
@ -289,6 +290,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
* @param returnType the returnType search parameter
* @param kinds the list suggestion kinds to search
* @param position the absolute position in the text
* @param isStatic the static attiribute
* @return the list of suggestion ids, ranked by specificity (as for
* `selfType`)
*/
@ -297,7 +299,8 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
selfType: Seq[String],
returnType: Option[String],
kinds: Option[Seq[Suggestion.Kind]],
position: Option[Suggestion.Position]
position: Option[Suggestion.Position],
isStatic: Option[Boolean]
): DBIO[(Long, Seq[Long])] = {
val typeSorterMap: HashMap[String, Int] = HashMap(selfType.zipWithIndex: _*)
val searchAction =
@ -306,12 +309,20 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
selfType.isEmpty &&
returnType.isEmpty &&
kinds.isEmpty &&
position.isEmpty
position.isEmpty &&
isStatic.isEmpty
) {
DBIO.successful(Seq())
} else {
val query =
searchQueryBuilder(module, selfType, returnType, kinds, position)
searchQueryBuilder(
module,
selfType,
returnType,
kinds,
position,
isStatic
)
.map(r => (r.id, r.selfType))
query.result
}
@ -917,6 +928,7 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
* @param returnType the returnType search parameter
* @param kinds the list suggestion kinds to search
* @param position the absolute position in the text
* @param isStatic the static attribute
* @return the search query
*/
private def searchQueryBuilder(
@ -924,13 +936,16 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
selfTypes: Seq[String],
returnType: Option[String],
kinds: Option[Seq[Suggestion.Kind]],
position: Option[Suggestion.Position]
position: Option[Suggestion.Position],
isStatic: Option[Boolean]
): Query[SuggestionsTable, SuggestionRow, Seq] = {
Suggestions
.filterOpt(module) { case (row, value) =>
row.scopeStartLine === ScopeColumn.EMPTY || row.module === value
}
.filterIf(selfTypes.nonEmpty) { row => row.selfType.inSet(selfTypes) }
.filterIf(selfTypes.nonEmpty) { row =>
row.selfType.inSet(selfTypes)
}
.filterOpt(returnType) { case (row, value) =>
row.returnType === value
}
@ -944,6 +959,9 @@ final class SqlSuggestionsRepo(val db: SqlDatabase)(implicit
row.scopeEndLine >= value.line
)
}
.filterOpt(isStatic) { case (row, value) =>
row.isStatic === value
}
}
/** Convert the rows of suggestions joined with arguments to a list of

View File

@ -1441,7 +1441,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(None, Seq(), None, None, None)
res <- repo.search(None, Seq(), None, None, None, None)
} yield res._2
val res = Await.result(action, Timeout)
@ -1457,7 +1457,14 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
id4 <- repo.insert(suggestion.conversion)
id5 <- repo.insert(suggestion.function)
id6 <- repo.insert(suggestion.local)
res <- repo.search(Some("local.Test.Main"), Seq(), None, None, None)
res <- repo.search(
Some("local.Test.Main"),
Seq(),
None,
None,
None,
None
)
} yield (id0, id1, id2, id3, id4, id5, id6, res._2)
val (id0, id1, id2, id3, id4, id5, id6, res) =
@ -1482,7 +1489,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
id4 <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(Some(""), Seq(), None, None, None)
res <- repo.search(Some(""), Seq(), None, None, None, None)
} yield (res._2, Seq(id0, id1, id2, id3, id4))
val (res, globals) = Await.result(action, Timeout)
@ -1498,7 +1505,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(None, Seq("local.Test.Main"), None, None, None)
res <- repo.search(None, Seq("local.Test.Main"), None, None, None, None)
} yield (id2, res._2)
val (id, res) = Await.result(action, Timeout)
@ -1519,6 +1526,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq(),
Some("local.Test.Main.MyType"),
None,
None,
None
)
} yield (id3, id4, res._2)
@ -1537,7 +1545,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
id4 <- repo.insert(suggestion.local)
res <- repo.search(None, Seq(), None, Some(kinds), None)
res <- repo.search(None, Seq(), None, Some(kinds), None, None)
} yield (id1, id4, res._2)
val (id1, id2, res) = Await.result(action, Timeout)
@ -1553,7 +1561,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(None, Seq(), None, Some(Seq()), None)
res <- repo.search(None, Seq(), None, Some(Seq()), None, None)
} yield res._2
val res = Await.result(action, Timeout)
@ -1575,7 +1583,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq(),
None,
None,
Some(Suggestion.Position(99, 42))
Some(Suggestion.Position(99, 42)),
None
)
} yield (id0, id1, id2, id3, id4, res._2)
@ -1593,7 +1602,14 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
id5 <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <-
repo.search(None, Seq(), None, None, Some(Suggestion.Position(1, 5)))
repo.search(
None,
Seq(),
None,
None,
Some(Suggestion.Position(1, 5)),
None
)
} yield (id0, id1, id2, id3, id4, id5, res._2)
val (id0, id1, id2, id3, id4, id5, res) = Await.result(action, Timeout)
@ -1617,7 +1633,14 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
id5 <- repo.insert(suggestion.function)
id6 <- repo.insert(suggestion.local)
res <-
repo.search(None, Seq(), None, None, Some(Suggestion.Position(6, 0)))
repo.search(
None,
Seq(),
None,
None,
Some(Suggestion.Position(6, 0)),
None
)
} yield (id0, id1, id2, id3, id4, id5, id6, res._2)
val (id0, id1, id2, id3, id4, id5, id6, res) =
@ -1649,7 +1672,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq(),
None,
None,
Some(Suggestion.Position(2, 0))
Some(Suggestion.Position(2, 0)),
None
)
} yield (id0, id1, id2, id3, id4, id5, id6, res._2)
@ -1681,7 +1705,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq(),
None,
None,
Some(Suggestion.Position(4, 0))
Some(Suggestion.Position(4, 0)),
None
)
} yield (id0, id1, id2, id3, id4, id5, id6, res._2)
@ -1698,6 +1723,32 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
).flatten
}
"search suggestion by the static attribute" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(),
None,
None,
None,
Some(true)
)
} yield (id3, res._2)
val (id3, res) =
Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id3).flatten
}
"search suggestion by module and self type" taggedAs Retry in withRepo {
repo =>
val action = for {
@ -1713,6 +1764,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq("local.Test.Main"),
None,
None,
None,
None
)
} yield (id2, res._2)
@ -1721,6 +1773,32 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
res should contain theSameElementsAs Seq(id).flatten
}
"search suggestion by self type and static attribute" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(suggestion.method.selfType),
None,
None,
None,
Some(true)
)
} yield (id3, res._2)
val (id3, res) =
Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id3).flatten
}
"search suggestion by return type and kind" taggedAs Retry in withRepo {
repo =>
val kinds = Seq(Suggestion.Kind.Constructor, Suggestion.Kind.Local)
@ -1737,6 +1815,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq(),
Some("local.Test.Main.MyType"),
Some(kinds),
None,
None
)
} yield (id4, res._2)
@ -1760,7 +1839,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq(),
Some("local.Test.Main.MyType"),
None,
Some(Suggestion.Position(6, 0))
Some(Suggestion.Position(6, 0)),
None
)
} yield (id3, id4, res._2)
@ -1783,7 +1863,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq(),
None,
Some(kinds),
Some(Suggestion.Position(99, 1))
Some(Suggestion.Position(99, 1)),
None
)
} yield (id1, res._2)
@ -1806,6 +1887,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq("local.Test.Main"),
Some("local.Test.Main.MyType"),
None,
None,
None
)
} yield res._2
@ -1830,6 +1912,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq(),
Some("local.Test.Main.MyType"),
Some(kinds),
None,
None
)
} yield (id4, res._2)
@ -1854,7 +1937,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq(),
Some("local.Test.Main.MyType"),
Some(kinds),
Some(Suggestion.Position(6, 0))
Some(Suggestion.Position(6, 0)),
None
)
} yield (id4, res._2)
@ -1881,7 +1965,8 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
Seq("local.Test.Main"),
Some("local.Test.Main.MyType"),
Some(kinds),
Some(Suggestion.Position(42, 0))
Some(Suggestion.Position(42, 0)),
Some(true)
)
} yield res._2