@@ -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? 
537472static  ({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