Skip to content
This repository was archived by the owner on Sep 29, 2023. It is now read-only.

Commit f3ceb83

Browse files
author
jchapuis
committed
added meta-data support at the level of the completions
1 parent e3abfe2 commit f3ceb83

File tree

7 files changed

+107
-53
lines changed

7 files changed

+107
-53
lines changed

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name := "scala-parser-combinators-completion"
22
organization := "com.nexthink"
33
licenses += ("MIT", url("http://opensource.org/licenses/MIT"))
4-
version := "1.0.5"
4+
version := "1.0.6"
55
scalaVersion := "2.12.2"
66
bintrayRepository := "maven"
77
bintrayVcsUrl := Some("jchapuis@github.com:jchapuis/scala-parser-combinators-completion")

src/main/scala/com/nexthink/utils/parsing/combinator/completion/CompletionExpansionSupport.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ trait CompletionExpansionSupport extends RegexCompletionSupport {
3939
in => {
4040
lazy val isAtInputEnd = dropAnyWhiteSpace(in).atEnd
4141
if (!onlyAtInputEnd || isAtInputEnd) {
42-
val Completions(_, sets) = exploreCompletions(p, limiter, in)
43-
Completions(OffsetPosition(in.source, handleWhiteSpace(in)), sets)
42+
val Completions(_, meta, sets) = exploreCompletions(p, limiter, in)
43+
Completions(OffsetPosition(in.source, handleWhiteSpace(in)), meta, sets)
4444
} else
4545
p.completions(in)
4646
}

src/main/scala/com/nexthink/utils/parsing/combinator/completion/CompletionSupport.scala

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ trait CompletionSupport extends Parsers with CompletionTypes {
117117
this,
118118
in => {
119119
val completions = this.completions(in)
120-
Completions(completions.position, completions.sets.mapValues(s => CompletionSet(s.tag, s.entries.toList.sortBy(_.score).reverse.take(n).toSet)).toSeq)
120+
Completions(
121+
completions.position,
122+
completions.meta,
123+
completions.sets.mapValues(s => CompletionSet(s.tag, s.entries.toList.sortBy(_.score).reverse.take(n).toSet)).toSeq
124+
)
121125
}
122126
)
123127

@@ -126,22 +130,22 @@ trait CompletionSupport extends Parsers with CompletionTypes {
126130
* @return wrapper `Parser` instance specifying the completion tag
127131
*/
128132
def %(tag: String): Parser[T] =
129-
Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag), None, None, None))
133+
Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag)))
130134

131135
/** An operator to specify the completions tag score of a parser (0 by default)
132136
* @param tagScore the completion tag score (to be used e.g. to order sections in a completion menu)
133137
* @return wrapper `Parser` instance specifying the completion tag score
134138
*/
135139
def %(tagScore: Int): Parser[T] =
136-
Parser(this, in => updateCompletionsTag(this.completions(in), None, Some(tagScore), None, None))
140+
Parser(this, in => updateCompletionsTag(this.completions(in), None, Some(tagScore)))
137141

138142
/** An operator to specify the completion tag and score of a parser
139143
* @param tag the completion tag
140144
* @param tagScore the completion tag score
141145
* @return wrapper `Parser` instance specifying the completion tag
142146
*/
143147
def %(tag: String, tagScore: Int): Parser[T] =
144-
Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), None, None))
148+
Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore)))
145149

146150
/** An operator to specify the completion tag, score and description of a parser
147151
* @param tag the completion tag
@@ -150,17 +154,17 @@ trait CompletionSupport extends Parsers with CompletionTypes {
150154
* @return wrapper `Parser` instance specifying completion tag
151155
*/
152156
def %(tag: String, tagScore: Int, tagDescription: String): Parser[T] =
153-
Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), Some(tagDescription), None))
157+
Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), Some(tagDescription)))
154158

155159
/** An operator to specify the completion tag, score, description and meta of a parser
156160
* @param tag the completion tag
157161
* @param tagScore the completion tag score
158162
* @param tagDescription the completion tag description
159-
* @param tagKind the completion tag meta
163+
* @param tagMeta the completion tag meta
160164
* @return wrapper `Parser` instance specifying completion tag
161165
*/
162-
def %(tag: String, tagScore: Int, tagDescription: String, tagKind: String): Parser[T] =
163-
Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), Some(tagDescription), Some(tagKind)))
166+
def %(tag: String, tagScore: Int, tagDescription: String, tagMeta: String): Parser[T] =
167+
Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), Some(tagDescription), Some(tagMeta)))
164168

165169
/** An operator to specify the completion tag
166170
* @param tag the completion tag
@@ -174,7 +178,7 @@ trait CompletionSupport extends Parsers with CompletionTypes {
174178
* @return wrapper `Parser` instance specifying the completion description
175179
*/
176180
def %?(tagDescription: String): Parser[T] =
177-
Parser(this, in => updateCompletionsTag(this.completions(in), None, None, Some(tagDescription), None))
181+
Parser(this, in => updateCompletionsTag(this.completions(in), None, None, Some(tagDescription)))
178182

179183
/** An operator to specify the completion tag meta-data of a parser (empty by default).
180184
* Note that meta-data is merged with comma separations when combining two equivalent entries.
@@ -191,15 +195,16 @@ trait CompletionSupport extends Parsers with CompletionTypes {
191195
* @param tagMeta the JValue for completion tag meta-data (to be used e.g. to specify the visual style for a completion tag in the menu)
192196
* @return wrapper `Parser` instance specifying the completion tag meta-data
193197
*/
194-
def %%(tagMeta: JValue): Parser[T] = %%(compact(render(tagMeta)))
198+
def %%(tagMeta: JValue): Parser[T] =
199+
Parser(this, in => updateCompletionsSets(this.completions(in), set => CompletionSet(set.tag.updateMeta(tagMeta), set.completions)))
195200

196201
/** An operator to specify the meta-data for completions of a parser (empty by default).
197202
* Note that meta-data is merged with comma separations when combining two equivalent entries.
198203
* @param meta the completion meta-data (to be used e.g. to specify the visual style for a completion entry in the menu)
199204
* @return wrapper `Parser` instance specifying the completion meta-data
200205
*/
201206
def %-%(meta: String): Parser[T] =
202-
Parser(this, in => updateCompletions(this.completions(in), Some(meta)))
207+
Parser(this, in => updateCompletionsSets(this.completions(in), set => CompletionSet(set.tag, set.entries.map(e => e.updateMeta(meta)))))
203208

204209
/** An operator to specify the meta-data for completions of a parser in JSON format (empty by default)
205210
* Note that if the meta-data is encoded in JSON, it is automatically merged when combining two equivalent entries
@@ -210,6 +215,26 @@ trait CompletionSupport extends Parsers with CompletionTypes {
210215
*/
211216
def %-%(meta: JValue): Parser[T] = %-%(compact(render(meta)))
212217

218+
/**
219+
* An operator to specify the meta-data for the whole set of completions (empty by default)
220+
* Note that meta-data is merged with comma separations when combining two equivalent entries.
221+
* @param globalMeta the meta-data (to be used e.g. to specify the visual style for the completion menu)
222+
* @return wrapper `Parser` instance specifying the completions meta-data
223+
*/
224+
def %%%(globalMeta: String): Parser[T] =
225+
Parser(this, in => this.completions(in).updateMeta(globalMeta))
226+
227+
/**
228+
* An operator to specify the meta-data for the whole set of completions (empty by default)
229+
* Note that if the meta-data is encoded in JSON, it is automatically merged when combining multiple completion sets.
230+
* This allows for more flexibility when defining the grammar: various parsers can define the global completion meta-data
231+
* with an additive effect.
232+
* @param globalMeta the JValue for completions meta-data (to be used e.g. to specify the visual style for the completion menu)
233+
* @return wrapper `Parser` instance specifying the completions meta-data
234+
*/
235+
def %%%(globalMeta: JValue): Parser[T] =
236+
Parser(this, in => this.completions(in).updateMeta(globalMeta))
237+
213238
def flatMap[U](f: T => Parser[U]): Parser[U] =
214239
Parser(super.flatMap(f), completions)
215240

@@ -233,26 +258,27 @@ trait CompletionSupport extends Parsers with CompletionTypes {
233258

234259
private def updateCompletionsSets(completions: Completions, updateSet: CompletionSet => CompletionSet) = {
235260
Completions(completions.position,
261+
completions.meta,
236262
completions.sets.values
237263
.map(updateSet)
238264
.map(s => s.tag.label -> s)
239265
.toSeq)
240266
}
241267

242268
private def updateCompletionsTag(completions: Completions,
243-
newTagLabel: Option[String],
244-
newTagScore: Option[Int],
245-
newTagDescription: Option[String],
246-
newTagKind: Option[String]) = {
269+
newTagLabel: Option[String] = None,
270+
newTagScore: Option[Int] = None,
271+
newTagDescription: Option[String] = None,
272+
newTagKind: Option[String] = None) = {
247273
def updateSet(existingSet: CompletionSet) =
248274
CompletionSet(existingSet.tag.update(newTagLabel, newTagScore, newTagDescription, newTagKind), existingSet.completions)
249275

250276
updateCompletionsSets(completions, updateSet)
251277
}
252278

253-
private def updateCompletions(completions: Completions, newCompletionKind: Option[String]) = {
279+
private def updateCompletionsMeta(completions: Completions, newMeta: String) = {
254280
def updateSet(existingSet: CompletionSet) =
255-
CompletionSet(existingSet.tag, existingSet.entries.map(e => e.updateKind(newCompletionKind)))
281+
CompletionSet(existingSet.tag, existingSet.entries.map(e => e.updateMeta(newMeta)))
256282
updateCompletionsSets(completions, updateSet)
257283
}
258284

src/main/scala/com/nexthink/utils/parsing/combinator/completion/CompletionTypes.scala

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ trait CompletionTypes {
5555
meta = newMeta.map(Some(_)).getOrElse(meta)
5656
)
5757

58+
def updateMeta(newMeta: JValue): CompletionTag = copy(meta = Some(encodeJson(newMeta)))
59+
5860
private[CompletionTypes] def serializeJson: json4s.JObject = {
5961
("label" -> label) ~ ("score" -> score) ~ ("description" -> description) ~ ("meta" -> meta)
6062
}
6163

62-
override def toString: String = pretty(render(serializeJson))
64+
override def toString: String = printJson(serializeJson)
6365
def toJson: JValue = serializeJson
6466
}
6567

@@ -70,6 +72,8 @@ trait CompletionTypes {
7072
CompletionTag(label, DefaultCompletionScore, None, None)
7173
def apply(label: String, score: Int): CompletionTag =
7274
CompletionTag(label, score, None, None)
75+
def apply(label: String, score: Int, description: String): CompletionTag =
76+
CompletionTag(label, score, Some(description), None)
7377
}
7478

7579
/** Set of related completion entries
@@ -88,7 +92,7 @@ trait CompletionTypes {
8892
private[CompletionTypes] def serializeJson =
8993
("tag" -> tag.serializeJson) ~ ("completions" -> entries.map(_.serializeJson).toList)
9094

91-
override def toString: String = pretty(render(serializeJson))
95+
override def toString: String = printJson(serializeJson)
9296
def toJson: JValue = serializeJson
9397
}
9498

@@ -133,14 +137,14 @@ trait CompletionTypes {
133137
*/
134138
case class Completion(value: Elems, score: Int = DefaultCompletionScore, meta: Option[String] = None) {
135139
require(value.nonEmpty, "empty completion")
136-
def updateKind(newMeta: Option[String]): Completion =
137-
copy(meta = newMeta.map(Some(_)).getOrElse(meta))
140+
def updateMeta(newMeta: JValue): Completion = updateMeta(encodeJson(newMeta))
141+
def updateMeta(newMeta: String): Completion = copy(meta = Some(newMeta))
138142

139143
private[CompletionTypes] def serializeJson = ("value" -> value.toString()) ~ ("score" -> score) ~ ("meta" -> meta)
140144

141-
def toJson: String = compact(render(serializeJson))
145+
def toJson: String = encodeJson(serializeJson)
142146

143-
override def toString: String = pretty(render(serializeJson))
147+
override def toString: String = printJson(serializeJson)
144148
}
145149
case object Completion {
146150
def apply(el: Elem): Completion = Completion(Seq(el))
@@ -152,24 +156,27 @@ trait CompletionTypes {
152156
* @param position position in the input where completion entries apply
153157
* @param sets completion entries, grouped per tag
154158
*/
155-
case class Completions(position: Position, sets: immutable.HashMap[String, CompletionSet]) {
159+
case class Completions(position: Position, meta: Option[String], sets: immutable.HashMap[String, CompletionSet]) {
156160
def isEmpty: Boolean = sets.isEmpty
157161
def nonEmpty: Boolean = !isEmpty
158162
def setWithTag(tag: String): Option[CompletionSet] = sets.get(tag)
159163
def allSets: Iterable[CompletionSet] = sets.values.toSeq.sorted
160164
def allCompletions: Iterable[Completion] = allSets.flatMap(_.sortedEntries)
161165
def defaultSet: Option[CompletionSet] = sets.get("")
166+
def updateMeta(newMeta: JValue): Completions = updateMeta(encodeJson(newMeta))
167+
def updateMeta(newMeta: String): Completions = copy(meta = Some(newMeta))
162168

163-
private def serializeJson = ("position" -> (("line" -> position.line) ~ ("column" -> position.column))) ~ ("sets" -> allSets.map(_.serializeJson))
169+
private def serializeJson =
170+
("position" -> (("line" -> position.line) ~ ("column" -> position.column))) ~ ("meta" -> meta) ~ ("sets" -> allSets.map(_.serializeJson))
164171

165-
override def toString: String = pretty(render(serializeJson))
172+
override def toString: String = printJson(serializeJson)
166173
def toJson: JValue = serializeJson
167174
def setsToJson: JArray = allSets.map(_.serializeJson)
168175

169176
private def mergeMetaData(left: Option[String], right: Option[String]) = (left, right) match {
170177
case (Some(l), Some(r)) =>
171178
(parseOpt(l), parseOpt(r)) match {
172-
case (Some(lJson), Some(rJson)) => Some(compact(render(lJson merge rJson)))
179+
case (Some(lJson), Some(rJson)) => Some(encodeJson(lJson merge rJson))
173180
case _ => Some(Seq(l, r).mkString(", "))
174181
}
175182
case (Some(l), None) => Some(l)
@@ -199,9 +206,10 @@ trait CompletionTypes {
199206
case Completions.empty => this
200207
case _ =>
201208
other.position match {
202-
case otherPos if otherPos < position => this
203-
case otherPos if otherPos == position => Completions(position, sets.merged(other.sets)((l, r) => (l._1, mergeSets(l._2, r._2))))
204-
case _ => other
209+
case otherPos if otherPos < position => this
210+
case otherPos if otherPos == position =>
211+
Completions(position, mergeMetaData(meta, other.meta), sets.merged(other.sets)((l, r) => (l._1, mergeSets(l._2, r._2))))
212+
case _ => other
205213
}
206214
}
207215
}
@@ -231,29 +239,38 @@ trait CompletionTypes {
231239
case (groupTag, completions) =>
232240
CompletionSet(groupTag, completions.map(c => c._1))
233241
}
234-
Completions(position, regroupedSets.map(s => s.tag.label -> s).toSeq)
242+
Completions(position, meta, regroupedSets.map(s => s.tag.label -> s).toSeq)
235243
}
236244

237245
def setsScoredWithMaxCompletion(): Completions = {
238-
Completions(position, sets.mapValues(s => CompletionSet(s.tag.copy(score = s.completions.values.map(_.score).max), s.completions)).toSeq)
246+
Completions(position, meta, sets.mapValues(s => CompletionSet(s.tag.copy(score = s.completions.values.map(_.score).max), s.completions)).toSeq)
239247
}
240248
}
241249

250+
private def encodeJson(meta: JValue) = compact(render(meta))
251+
private def printJson(meta: JValue) = pretty(render(meta))
252+
242253
case object Completions {
243-
def apply(position: Position, completionSets: Seq[(String, CompletionSet)]): Completions =
244-
Completions(position, immutable.HashMap(completionSets: _*))
254+
def apply(position: Position, meta: Option[String], completionSets: Seq[(String, CompletionSet)]): Completions =
255+
Completions(position, meta, immutable.HashMap(completionSets: _*))
245256
def apply(position: Position, completionSet: CompletionSet): Completions =
246-
Completions(position, Seq(completionSet.tag.label -> completionSet))
257+
Completions(position, None, Seq(completionSet.tag.label -> completionSet))
258+
def apply(position: Position, meta: Option[String], completionSet: CompletionSet): Completions =
259+
Completions(position, None, Seq(completionSet.tag.label -> completionSet))
260+
def apply(position: Position, meta: Option[String], completions: Traversable[Elems]): Completions =
261+
Completions(position, meta, CompletionSet(completions))
247262
def apply(position: Position, completions: Traversable[Elems]): Completions =
248-
Completions(position, CompletionSet(completions))
249-
def apply(completionSet: CompletionSet): Completions =
250-
Completions(NoPosition, completionSet)
263+
Completions(position, None, CompletionSet(completions))
264+
def apply(position: Position, meta:Option[String], completionSets: Iterable[CompletionSet]): Completions =
265+
Completions(position, meta, completionSets.map(s => s.tag.label -> s).toSeq)
251266
def apply(position: Position, completionSets: Iterable[CompletionSet]): Completions =
252-
Completions(position, completionSets.map(s => s.tag.label -> s).toSeq)
267+
Completions(position, None, completionSets.map(s => s.tag.label -> s).toSeq)
268+
def apply(completionSet: CompletionSet): Completions =
269+
Completions(NoPosition, None, completionSet)
253270
def apply(completionSets: Iterable[CompletionSet]): Completions =
254-
Completions(NoPosition, completionSets.map(s => s.tag.label -> s).toSeq)
271+
Completions(NoPosition, None, completionSets.map(s => s.tag.label -> s).toSeq)
255272

256-
val empty = Completions(NoPosition, immutable.HashMap[String, CompletionSet]())
273+
val empty = Completions(NoPosition, None, immutable.HashMap[String, CompletionSet]())
257274
}
258275

259276
}

src/test/scala/com/nexthink/utils/parsing/combinator/completion/CompletionExpansionSupportTest.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class CompletionExpansionSupportTest {
4141
val jumpsOver = "which jumps over the lazy" % "action"
4242
val jumpsOverDogOrCat = jumpsOver ~ ("dog" | "cat") % "animal" %? "dogs and cats" % 10
4343
lazy val parser = jumpsOverDogOrCat | jumpsOverDogOrCat ~ which()
44-
def which(): Parser[Any] = expandedCompletionsWithLimiter(parser, limiter = jumpsOverDogOrCat ~ jumpsOverDogOrCat)
44+
def which(): Parser[Any] = expandedCompletionsWithLimiter(parser, limiter = jumpsOverDogOrCat ~ jumpsOverDogOrCat) %%% "expansions"
4545
lazy val infiniteDogsAndCats = fox ~ which
4646
}
4747

@@ -73,6 +73,12 @@ class CompletionExpansionSupportTest {
7373
)
7474
}
7575

76+
@Test
77+
def infiniteExpressionExpansionIncludesGlobalMeta(): Unit = {
78+
val completions = InfiniteExpressionParser.complete(InfiniteExpressionParser.infiniteDogsAndCats, "the quick brown fox ")
79+
Assert.assertEquals(Some("expansions"), completions.meta)
80+
}
81+
7682
@Test
7783
def expanderWithOnlyAtInputEndDoesNotExpandElsewhere(): Unit = {
7884
val completions = InfiniteExpressionParser.completeString(InfiniteExpressionParser.infiniteDogsAndCats, "the quick brown fox which jumps over the lazy")

src/test/scala/com/nexthink/utils/parsing/combinator/completion/CompletionOperatorsTest.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,17 @@ class CompletionOperatorsTest {
9494
@Test
9595
def topCompletionsLimitsCompletionsAccordingToScore(): Unit = {
9696
// Arrange
97+
val meta = "meta"
9798
val completions = Seq("one", "two", "three", "four").zipWithIndex.map {
9899
case (c, s) => TestParser.Completion(c, s)
99100
}
100-
val sut = (TestParser.someParser %> completions).topCompletions(2)
101+
val sut = (TestParser.someParser %> completions %%% meta).topCompletions(2)
101102

102103
// Act
103104
val result = TestParser.complete(sut, "")
104105

105106
// Assert
106107
Assert.assertArrayEquals(Seq("four", "three").toArray[AnyRef], result.completionStrings.toArray[AnyRef])
108+
Assert.assertEquals(Some(meta), result.meta)
107109
}
108110
}

0 commit comments

Comments
 (0)