@@ -7,6 +7,7 @@ import 'dart:convert';
77import 'dart:js_interop' ;
88import 'dart:math' as math;
99
10+ import 'package:_pub_shared/data/completion.dart' ;
1011import 'package:collection/collection.dart' ;
1112import 'package:http/http.dart' deferred as http show read;
1213import 'package:web/web.dart' ;
@@ -63,7 +64,7 @@ void create(HTMLElement element, Map<String, String> options) {
6364 await input.onFocus.first;
6465 }
6566
66- final _CompletionData data;
67+ final CompletionData data;
6768 try {
6869 data = await _CompletionWidget ._completionDataFromUri (srcUri);
6970 } on Exception catch (e) {
@@ -91,13 +92,6 @@ void create(HTMLElement element, Map<String, String> options) {
9192 });
9293}
9394
94- typedef _CompletionData = List <
95- ({
96- Set <String > match,
97- List <String > options,
98- bool terminal,
99- bool forcedOnly,
100- })>;
10195typedef _Suggestions = List <
10296 ({
10397 String value,
@@ -178,7 +172,7 @@ final class _CompletionWidget {
178172
179173 final HTMLInputElement input;
180174 final HTMLDivElement dropdown;
181- final _CompletionData data;
175+ final CompletionData data;
182176 var state = _State ();
183177
184178 _CompletionWidget ._({
@@ -451,73 +445,14 @@ final class _CompletionWidget {
451445 /// Ideally, an end-point serving this kind of completion data should have
452446 /// `Cache-Control` headers that allow caching for a decent period of time.
453447 /// Compression with `gzip` (or similar) would probably also be wise.
454- static Future <_CompletionData > _completionDataFromUri (Uri src) async {
448+ static Future <CompletionData > _completionDataFromUri (Uri src) async {
455449 await http.loadLibrary ();
456450 final root = jsonDecode (
457451 await http.read (src, headers: {
458452 'Accept' : 'application/json' ,
459453 }).timeout (Duration (seconds: 30 )),
460454 );
461- return _completionDataFromJson (root);
462- }
463-
464- /// Load completion data from [json] .
465- ///
466- /// Completion data must be JSON on the form:
467- /// ```js
468- /// {
469- /// "completions": [
470- /// {
471- /// // The match trigger automatic completion (except empty match).
472- /// // Example: `platform:` or `platform:win`
473- /// // Match and an option must be combined to form a keyword.
474- /// // Example: `platform:windows`
475- /// "match": ["platform:", "-platform:"],
476- /// "forcedOnly": false, // Only display this when forced to match
477- /// "terminal": true, // Add whitespace when completing
478- /// "options": [
479- /// "linux",
480- /// "windows",
481- /// "android",
482- /// "ios",
483- /// ...
484- /// ],
485- /// },
486- /// ...
487- /// ],
488- /// }
489- /// ```
490- static _CompletionData _completionDataFromJson (Object ? json) {
491- if (json is ! Map ) throw FormatException ('root must be a object' );
492- final completions = json['completions' ];
493- if (completions is ! List ) {
494- throw FormatException ('completions must be a list' );
495- }
496- return completions.map ((e) {
497- if (e is ! Map ) throw FormatException ('completion entries must be object' );
498- final terminal = e['terminal' ] ?? true ;
499- if (terminal is ! bool ) throw FormatException ('termianl must be bool' );
500- final forcedOnly = e['forcedOnly' ] ?? false ;
501- if (forcedOnly is ! bool ) throw FormatException ('forcedOnly must be bool' );
502- final match = e['match' ];
503- if (match is ! List ) throw FormatException ('match must be a list' );
504- final options = e['options' ];
505- if (options is ! List ) throw FormatException ('options must be a list' );
506- return (
507- match: match
508- .map ((m) => m is String
509- ? m
510- : throw FormatException ('match must be strings' ))
511- .toSet (),
512- forcedOnly: forcedOnly,
513- terminal: terminal,
514- options: options
515- .map ((option) => option is String
516- ? option
517- : throw FormatException ('options must be strings' ))
518- .toList (),
519- );
520- }).toList ();
455+ return CompletionData .fromJson (root as Map <String , dynamic >);
521456 }
522457
523458 static late final _canvas = HTMLCanvasElement ();
@@ -535,7 +470,7 @@ final class _CompletionWidget {
535470 /// Given [data] and [caret] position inside [text] what suggestions do we
536471 /// want to offer and should completion be automatically triggered?
537472 static ({bool trigger, _Suggestions suggestions}) suggest (
538- _CompletionData data,
473+ CompletionData data,
539474 String text,
540475 int caret,
541476 ) {
@@ -556,7 +491,7 @@ final class _CompletionWidget {
556491 } else {
557492 // If the part before the caret is matched, then we can auto trigger
558493 final wordBeforeCaret = text.substring (start, caret);
559- trigger = data.any (
494+ trigger = data.completions. any (
560495 (c) => ! c.forcedOnly && c.match.any (wordBeforeCaret.startsWith),
561496 );
562497 }
@@ -565,7 +500,7 @@ final class _CompletionWidget {
565500 final word = text.substring (start, end);
566501
567502 // Find the longest match for each completion entry
568- final completionWithBestMatch = data.map ((c) => (
503+ final completionWithBestMatch = data.completions. map ((c) => (
569504 completion: c,
570505 match: maxBy (c.match.where (word.startsWith), (m) => m.length),
571506 ));
0 commit comments