88 lines
2.2 KiB
Go
88 lines
2.2 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// Package spellcheck owns offline dictionary work and personal exceptions.
|
|
package spellcheck
|
|
|
|
/*
|
|
#cgo LDFLAGS: -ldl
|
|
#include "native.h"
|
|
#include <stdlib.h>
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"unsafe"
|
|
)
|
|
|
|
// Dictionary is used only by the service's worker, including Close.
|
|
type Dictionary interface {
|
|
Check(string) (bool, error)
|
|
Suggest(string) ([]string, error)
|
|
Close()
|
|
}
|
|
|
|
type EnchantDictionary struct {
|
|
native *C.SpellDictionary
|
|
}
|
|
|
|
func open_dictionary(language string) (Dictionary, error) {
|
|
if language == "" {
|
|
return nil, fmt.Errorf("the session has no spelling language")
|
|
}
|
|
tag := C.CString(language)
|
|
defer C.free(unsafe.Pointer(tag))
|
|
var status C.int
|
|
native := C.spell_dictionary_open(tag, &status)
|
|
if native == nil {
|
|
switch status {
|
|
case 1:
|
|
return nil, fmt.Errorf("Enchant 2 could not be loaded")
|
|
case 2:
|
|
return nil, fmt.Errorf("Enchant 2 has an incompatible interface")
|
|
case 4:
|
|
return nil, fmt.Errorf("dictionary for %s is unavailable", language)
|
|
default:
|
|
return nil, fmt.Errorf("could not initialize Enchant")
|
|
}
|
|
}
|
|
return &EnchantDictionary{native: native}, nil
|
|
}
|
|
|
|
func (d *EnchantDictionary) Check(word string) (bool, error) {
|
|
value := C.CString(word)
|
|
defer C.free(unsafe.Pointer(value))
|
|
result := C.spell_dictionary_check(d.native, value, C.size_t(len(word)))
|
|
if result < 0 {
|
|
return false, fmt.Errorf("dictionary check failed")
|
|
}
|
|
return result == 0, nil
|
|
}
|
|
|
|
func (d *EnchantDictionary) Suggest(word string) ([]string, error) {
|
|
value := C.CString(word)
|
|
defer C.free(unsafe.Pointer(value))
|
|
var count C.size_t
|
|
list := C.spell_dictionary_suggest(d.native, value, C.size_t(len(word)), &count)
|
|
if list == nil {
|
|
return nil, nil
|
|
}
|
|
defer C.spell_dictionary_free_suggestions(d.native, list)
|
|
// Preserve provider ranking, with a bounded result set for the picker.
|
|
var suggestions []string
|
|
seen := make(map[string]bool)
|
|
for _, item := range unsafe.Slice(list, int(min(count, 128))) {
|
|
word := C.GoString(item)
|
|
if word != "" && !seen[word] {
|
|
seen[word] = true
|
|
suggestions = append(suggestions, word)
|
|
}
|
|
}
|
|
return suggestions, nil
|
|
}
|
|
|
|
func (d *EnchantDictionary) Close() {
|
|
C.spell_dictionary_close(d.native)
|
|
}
|