ffuzzy 0.5.0 copy "ffuzzy: ^0.5.0" to clipboard
ffuzzy: ^0.5.0 copied to clipboard

Fast fuzzy search for Flutter, powered by a compact C engine via dart:ffi: fuzzy/substring/prefix/postfix/exact matching over a resident corpus, multi-threading, async filtering, hit highlighting, and [...]

example/lib/main.dart

// Minimal demo of the ffz fuzzy matcher: type in the box to filter a list,
// with matched characters highlighted (highlight: true → FuzzyHit.indices).
import 'package:flutter/material.dart';
import 'package:ffuzzy/ffuzzy.dart';

void main() => runApp(const FuzzyDemoApp());

const _items = <String>[
  'lib/src/widgets/scaffold.dart',
  'lib/src/material/app_bar.dart',
  'packages/ffz/lib/ffz.dart',
  'README.md',
  'CHANGELOG.md',
  '中文搜索引擎', // findable by pinyin via addKey below
  'café_menu.json',
  'src/main.rs',
];

class FuzzyDemoApp extends StatelessWidget {
  const FuzzyDemoApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'ffz demo',
        theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
        home: const SearchPage(),
      );
}

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});
  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  late final FuzzyCorpus<String> _corpus;
  List<FuzzyHit<String>> _hits = const [];
  int _searchGen = 0; // bumped per keystroke so only the latest result is shown

  @override
  void initState() {
    super.initState();
    _corpus = FuzzyCorpus.strings(_items, matchPaths: true);
    // Index the CJK item by host-computed pinyin/initials so latin typing finds it.
    _corpus.addKey('中文搜索引擎', [
      FuzzyKey.kind('zhongwensousuoyinqing', FuzzyKeyKind.pinyin),
      FuzzyKey.kind('zwssyq', FuzzyKeyKind.initials),
    ]);
    _search('');
  }

  Future<void> _search(String q) async {
    // fuzzyAsync keeps the UI smooth even for a large corpus. Because searches
    // can finish out of order under fast typing, tag each with a generation and
    // apply only the latest — so the displayed results always match the newest
    // query (never a stale one). (For a small corpus, a synchronous `_corpus
    // .fuzzy(q)` is simpler and inherently latest-wins.)
    final gen = ++_searchGen;
    final hits = await _corpus.fuzzyAsync(q, limit: 50, highlight: true);
    // Ignore if a newer keystroke superseded this, or the widget is gone.
    if (!mounted || gen != _searchGen) return;
    setState(() => _hits = hits);
  }

  @override
  void dispose() {
    _corpus.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('ffz fuzzy search')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: TextField(
              autofocus: true,
              decoration: const InputDecoration(
                hintText: 'Type to fuzzy-search (try "appbar", "中文", "zwssyq")',
                border: OutlineInputBorder(),
              ),
              onChanged: _search,
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _hits.length,
              itemBuilder: (context, i) {
                final hit = _hits[i];
                final text = hit.raw;
                final positions = hit.matchedKey == 0
                    ? fuzzyCodepointToUtf16(text, hit.indices).toSet()
                    : const <int>{};
                return ListTile(
                  dense: true,
                  title: _Highlighted(text, positions),
                  trailing: Text('${hit.score}'),
                  subtitle: hit.matchedKind == FuzzyKeyKind.original
                      ? null
                      : Text('via ${hit.matchedKind.name}'),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

class _Highlighted extends StatelessWidget {
  const _Highlighted(this.text, this.positions);
  final String text;
  final Set<int> positions;

  @override
  Widget build(BuildContext context) {
    final base = DefaultTextStyle.of(context).style;
    final hi = base.copyWith(
        fontWeight: FontWeight.bold,
        color: Theme.of(context).colorScheme.primary);
    final spans = <TextSpan>[];
    final units = text.codeUnits;
    for (var i = 0; i < units.length; i++) {
      spans.add(TextSpan(
          text: String.fromCharCode(units[i]),
          style: positions.contains(i) ? hi : base));
    }
    return Text.rich(TextSpan(children: spans));
  }
}
0
likes
150
points
334
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Fast fuzzy search for Flutter, powered by a compact C engine via dart:ffi: fuzzy/substring/prefix/postfix/exact matching over a resident corpus, multi-threading, async filtering, hit highlighting, and Unicode (CJK).

Repository (GitHub)
View/report issues

Topics

#fuzzy-search #ffi #matcher #search #unicode

License

MIT (license)

Dependencies

ffi, flutter

More

Packages that depend on ffuzzy

Packages that implement ffuzzy