Spellchecking + Logo

This commit is contained in:
Sarah Jamie Lewis 2022-10-09 16:39:25 -07:00
parent 96e4f0e32c
commit 27430fa5f2
14 changed files with 10586 additions and 107 deletions

BIN
assets/subwrite-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

10004
dicts/english.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,9 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
@ -8,6 +11,8 @@ import 'package:subwrite/annotation.dart';
import 'package:subwrite/section.dart';
import 'package:subwrite/sourcelink.dart';
import 'package:subwrite/spellcheck/spellchecker.dart';
import 'package:subwrite/view.dart';
import 'cursor_provider.dart';
import 'line_info.dart';
@ -16,16 +21,93 @@ class LineFile extends ChangeNotifier {
CursorProvider cursor = CursorProvider();
FocusNode focus = FocusNode();
bool showSuggestions = false;
late String projectPath;
late String path;
ItemScrollController scrollController = ItemScrollController();
var status = "";
var showSuggestions = false;
final SpellChecker spellchecker = SpellChecker.load("dicts/english.txt");
void cancelSuggestions() {
suggestionIndex = 0;
suggestLine = 0;
suggestCol = 0;
showSuggestions = false;
notifyListeners();
}
int suggestLine = 0;
int suggestCol = 0;
void showSuggestionsFor(int line, int col) {
if (showSuggestions == false) {
if (line != suggestLine || col != suggestCol) {
suggestLine = line;
suggestCol = col;
showSuggestions = true;
loadSuggestions();
notifyListeners();
}
}
}
ScrollController suggestionListController = ScrollController(initialScrollOffset: 0.0);
int suggestionIndex = 0;
List<Suggestion> suggestions = List.empty();
void suggestListUp() {
suggestionIndex = (suggestionIndex - 1) % suggestions.length;
if (suggestions.length - suggestionIndex >= 14 || suggestionIndex == 0 || suggestionIndex == suggestions.length - 1) {
suggestionListController.animateTo(suggestionIndex * 13.0, duration: const Duration(milliseconds: 100), curve: Curves.decelerate);
}
notifyListeners();
}
void suggestListDown() {
suggestionIndex = (suggestionIndex + 1) % suggestions.length;
if (suggestions.length - suggestionIndex >= 14 || suggestionIndex == 0 || suggestionIndex == suggestions.length - 1) {
suggestionListController.animateTo(suggestionIndex * 13.0, duration: const Duration(milliseconds: 100), curve: Curves.decelerate);
}
notifyListeners();
}
void suggestListInsert() {
// TODO replace
}
void loadSuggestions() {
List<Suggestion> suggest = List.empty(growable: true);
if (suggestLine > 0) {
backingLines[suggestLine - 1].annotations.forEach((element) {
print("${element.description} ${element.tool} $suggestCol ${element.sourceLink.colStart} ${element.sourceLink.colEnd}");
});
List<String> suggestions = backingLines[suggestLine - 1]
.annotations
.where((element) => element.tool == "spellcheck")
.where((element) => suggestCol >= element.sourceLink.colStart! && suggestCol < element.sourceLink.colEnd!)
.map((e) => e.description!)
.toList();
if (suggestions.isNotEmpty) {
for (var spell in suggestions.first.split(":")) {
suggest.add(Suggestion("spelling", spell));
}
this.suggestions = suggest;
notifyListeners();
}
}
}
void openFile(String path, int startLine) {
// reset path
this.path = path;
// purge backinglines...
backingLines = List.empty(growable: true);
status = "Opened $path";
File f = File(path);
var line = 0;
f.readAsLinesSync().forEach((text) {
@ -59,45 +141,67 @@ class LineFile extends ChangeNotifier {
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
cursor.moveCursor(-1, 0, backingLines, keepAnchor: event.isShiftPressed);
cursor.publishCursor(backingLines);
cancelSuggestions();
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
cursor.moveCursor(1, 0, backingLines, keepAnchor: event.isShiftPressed);
cursor.publishCursor(backingLines);
cancelSuggestions();
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
cursor.moveCursor(0, -1, backingLines, keepAnchor: event.isShiftPressed);
cursor.publishCursor(backingLines);
if (showSuggestions == false) {
cursor.moveCursor(0, -1, backingLines, keepAnchor: event.isShiftPressed);
cursor.publishCursor(backingLines);
} else {
suggestListUp();
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
cursor.moveCursor(0, 1, backingLines, keepAnchor: event.isShiftPressed);
cursor.publishCursor(backingLines);
if (showSuggestions == false) {
cursor.moveCursor(0, 1, backingLines, keepAnchor: event.isShiftPressed);
cursor.publishCursor(backingLines);
} else {
suggestListDown();
}
} else if (event.logicalKey == LogicalKeyboardKey.home) {
cancelSuggestions();
cursor.moveCursorToLineStart();
cursor.clearSelection();
cursor.publishCursor(backingLines);
} else if (event.logicalKey == LogicalKeyboardKey.end) {
cancelSuggestions();
cursor.moveCursorToLineEnd(backingLines[cursor.line - 1].text.length);
cursor.clearSelection();
cursor.publishCursor(backingLines);
} else if (event.logicalKey == LogicalKeyboardKey.enter) {
insertLine();
cursor.clearSelection();
cursor.publishCursor(backingLines);
if (showSuggestions == false) {
insertLine();
cursor.clearSelection();
cursor.publishCursor(backingLines);
} else {
suggestListInsert();
}
} else if (event.logicalKey == LogicalKeyboardKey.tab) {
insertChar(" ");
cursor.publishCursor(backingLines);
cancelSuggestions();
} else if (event.logicalKey == LogicalKeyboardKey.delete) {
handleDelete();
cursor.clearSelection();
cursor.publishCursor(backingLines);
cancelSuggestions();
} else if (event.logicalKey == LogicalKeyboardKey.backspace) {
handleBackspace();
cursor.clearSelection();
cursor.publishCursor(backingLines);
cancelSuggestions();
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
showSuggestions = false;
notifyListeners();
} else if (event.logicalKey == LogicalKeyboardKey.f4) {
spellCheckAll().then((value) {
status = "spellcheck complete";
});
} else {
if (event.isControlPressed) {
if (event.logicalKey == LogicalKeyboardKey.space) {
showSuggestions = true;
notifyListeners();
}
if (event.character != null && event.character == 's') {
@ -133,7 +237,7 @@ class LineFile extends ChangeNotifier {
for (var l in backingLines) {
content += '${l.text}\n';
for (var annot in l.annotations) {
if (annot.description != null) {
if (annot.description != null && annot.tool != "spellcheck") {
notes += '${annot.sourceLink.lineStart}:${annot.description}\n';
}
}
@ -278,6 +382,94 @@ class LineFile extends ChangeNotifier {
List<Section> sections = List.empty(growable: true);
Future<void> spellcheck(int i) async {
var line = backingLines[i];
line.annotations.removeWhere((element) => element.tool == "spellcheck");
List<LineAnnotation> annotations = List.empty(growable: true);
var wordStart = 0;
var wordEnd = 0;
var lowerCase = line.text.toLowerCase();
for (var c = 0; c < lowerCase.length; c++) {
if (lowerCase.substring(c, c + 1) == " ") {
wordEnd = c;
var annotationStart = wordStart;
var word = lowerCase.substring(wordStart, wordEnd);
wordStart = c + 1;
if (word.endsWith(".")) {
word = word.substring(0, word.length - 1);
}
if (word.endsWith(":")) {
word = word.substring(0, word.length - 1);
}
if (word.endsWith(";")) {
word = word.substring(0, word.length - 1);
}
if (word.endsWith(",")) {
word = word.substring(0, word.length - 1);
}
// clean up quotes
word = word.replaceAll("\"", "");
word = word.replaceAll("'", "");
word = word.replaceAll("`", "");
word = word.replaceAll("(", "");
word = word.replaceAll(")", "");
word = word.replaceAll("[", "");
word = word.replaceAll("]", "");
word = word.replaceAll("^", "");
word = word.replaceAll("*", "");
word = word.replaceAll("?", "");
word = word.replaceAll("!", "");
// ignore numbers
if (word.contains(RegExp(r'[0-9]'))) {
continue;
}
// ignore code...
if (word.contains("/") | word.contains("=") | word.contains(">") || word.contains("<") || word.contains("@") || word.contains("`") || word.contains(";")) {
continue;
}
if (word.length < 2 || word.length > 10) {
continue;
}
HashSet<String>? candidates = spellchecker.candidates(word);
if (candidates == null) {
await compute(mutations2, word).then((results) {
spellchecker.dictionary.putIfAbsent(word, () => HashSet());
results.forEach((element) {
if (spellchecker.known.contains(element)) {
spellchecker.dictionary[word]!.add(element);
}
});
});
}
// Candidates should now not be null...
candidates = spellchecker.candidates(word);
if (candidates!.contains(word) == false) {
annotations.add(LineAnnotation(SourceLink("", lineStart: 0, colStart: annotationStart, colEnd: wordEnd + 1, lineEnd: 0), "spellcheck", AnnotationType.highlight, "spellcheck",
description: candidates!.join(":")));
}
}
}
for (var annot in annotations) {
annot.sourceLink = SourceLink(path, lineStart: i + 1, lineEnd: i + 1, colStart: annot.sourceLink.colStart, colEnd: annot.sourceLink.colEnd);
line.annotations.add(annot);
}
notifyListeners();
return;
}
// rebuild the annotations manager
void parse() {
sections.clear();
@ -372,7 +564,9 @@ class LineFile extends ChangeNotifier {
void mouseDownUpdate(int line, int col) {
focus.requestFocus();
showSuggestions = false;
cursor.mouseDownUpdate(line, col, backingLines);
notifyListeners();
}
void mouseDownMoveUpdate(int col) {
@ -440,4 +634,21 @@ class LineFile extends ChangeNotifier {
int wordCount() {
return backingLines.map((e) => e.text.trim().split(" ").length).fold(0, (previousValue, element) => previousValue + element);
}
void setSuggestIndex(int index) {
suggestionIndex = index;
notifyListeners();
}
Future<bool> spellCheckAll() async {
for (int i = 0; i < backingLines.length; i++) {
await spellcheck(i);
}
return Future.value(true);
}
}
class SpellcheckerRequest {
final String text;
SpellcheckerRequest(this.text);
}

36
lib/ghostsense.dart Normal file
View File

@ -0,0 +1,36 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:subwrite/theme.dart';
import 'package:subwrite/view.dart';
import 'file.dart';
class Ghostsense extends StatefulWidget {
List<Suggestion> suggestions;
Ghostsense(this.suggestions);
@override
_Ghostsense createState() => _Ghostsense();
}
class _Ghostsense extends State<Ghostsense> {
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: ListView.builder(
controller: Provider.of<LineFile>(context).suggestionListController,
physics: const BouncingScrollPhysics(),
itemCount: widget.suggestions.length,
padding: EdgeInsets.all(2.0),
itemBuilder: (BuildContext bcontext, int index) {
return Listener(
onPointerDown: (event) {
// TODO do insert...
},
onPointerHover: (event) {
Provider.of<LineFile>(bcontext).setSuggestIndex(index);
},
child: Container(color: Provider.of<LineFile>(context).suggestionIndex == index ? sidebarHighlight : sidebar, child: widget.suggestions[index].getWidget()));
}));
}
}

View File

@ -48,6 +48,9 @@ class Highlighter {
case "code":
d.color = function;
break;
case "spellcheck":
d.color = variable;
d.underline = true;
}
decors.add(d);
@ -72,6 +75,9 @@ class Highlighter {
if (d.bold = true) {
style = style.copyWith(fontWeight: FontWeight.bold);
}
if (d.underline = true) {
style = style.copyWith(fontStyle: FontStyle.italic);
}
}
}

View File

@ -1,5 +1,6 @@
import 'dart:math';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:subwrite/theme.dart';
@ -37,7 +38,6 @@ class LineInfo extends ChangeNotifier {
return LayoutBuilder(
builder: (context, constraints) {
var highlightedLine = RichText(
text: TextSpan(
children: spans,
@ -88,12 +88,13 @@ class LineInfo extends ChangeNotifier {
var cursorPosTop = ((cursorStart! / maxCharsBeforeWrapping).floorToDouble() * lineHeight).roundToDouble();
var cursorPosLeft = max(0, (((cursorStart! % maxCharsBeforeWrapping) * charWidth) - charWidth / 2.0).roundToDouble());
TextStyle cursorStyle = TextStyle(fontFamily: 'Iosevka', fontSize: fontSize, color: foreground, backgroundColor: Colors.transparent, letterSpacing: -2.0);
var cursorPos = Positioned(top: cursorPosTop.toDouble(), left: leftMargin + cursorPosLeft.toDouble(), child: FadeTransition(opacity: animationController, child: Text("|", style: cursorStyle)));
var cursorPos =
Positioned(top: cursorPosTop.toDouble(), left: leftMargin + cursorPosLeft.toDouble(), child: FadeTransition(opacity: animationController, child: Text("|", style: cursorStyle)));
stackElements.add(cursorPos);
}
var cursorOverlay = Stack(children: stackElements);
print("rebuilding line $lineNumber $idealHeight");
//print("rebuilding line $lineNumber $idealHeight");
// print("$charWidth $lineHeight $usableSize $idealHeight ${constraints.maxWidth}");
return Listener(
@ -104,6 +105,24 @@ class LineInfo extends ChangeNotifier {
if (event.down && !inMargin) {
file.mouseDownUpdate(lineNumber, c.round());
}
var set = false;
if (event.buttons == kSecondaryMouseButton) {
for (var annot in annotations) {
if (annot.tool == "spellcheck") {
if (annot.sourceLink.colStart != null && annot.sourceLink.colEnd != null) {
if (annot.sourceLink.colStart! <= c && annot.sourceLink.colEnd! > c) {
set = true;
break;
}
}
}
}
if (set) {
Provider.of<LineFile>(context).showSuggestionsFor(lineNumber, c);
}
}
},
onPointerMove: (event) {
var inMargin = (event.localPosition.dx - gutterWidth) > usableSize;
@ -129,6 +148,7 @@ class LineInfo extends ChangeNotifier {
onHover: (event) {
var c = cursorFromMouse(usableSize, event.localPosition.dx - gutterWidth, event.localPosition.dy);
var file = Provider.of<LineFile>(context, listen: false);
if (event.down) {
// note should never happen because flutter only fires hover events when the mouse it up...
file.mouseDownUpdate(lineNumber, c.round());
@ -136,8 +156,7 @@ class LineInfo extends ChangeNotifier {
file.mouseDownEnd();
}
},
onExit: (event) {
},
onExit: (event) {},
child: Container(
color: backgroundStyle.backgroundColor,
padding: EdgeInsets.zero,

View File

@ -1,7 +1,10 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:subwrite/file.dart';
import 'package:subwrite/theme.dart';
import 'package:subwrite/view.dart';
class Outline extends StatefulWidget {
const Outline({super.key});
@ -11,69 +14,107 @@ class Outline extends StatefulWidget {
}
class _Outline extends State<Outline> {
final ScrollController controller = ScrollController();
@override
Widget build(BuildContext context) {
var sections = Provider.of<LineFile>(context).sections;
return Material(
color: tabs,
child: ReorderableListView.builder(
header: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
child: Column(children: [
Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(
Icons.edit,
color: foreground,
),
Text("subwrite", style: TextStyle(fontSize: 16.0, color: foreground, fontWeight: FontWeight.bold, fontFamily: "Iosevka"))
]),
Divider(
color: foreground,
)
])),
footer: Padding(padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 10.0), child: Divider(color: foreground, thickness: 4.0)),
onReorder: (int oldIndex, int newIndex) {
var doc = Provider.of<LineFile>(context, listen: false);
var startLine = sections[oldIndex].lineNumber;
child: LayoutBuilder(builder: (context, constraints) {
return Scrollbar(
controller: controller,
thumbVisibility: false,
trackVisibility: false,
child: ReorderableListView.builder(
scrollController: controller,
physics: AlwaysScrollableScrollPhysics(),
header: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
child: Column(children: [
Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [
// Icon(
// Icons.edit,
// color: foreground,
// ),
Image(
image: AssetImage("assets/subwrite-logo.png"),
filterQuality: FilterQuality.medium,
width: constraints.maxWidth / 3.0,
height: constraints.maxWidth / 3.0,
)
//Text("subwrite", style: TextStyle(fontSize: 16.0, color: foreground, fontWeight: FontWeight.bold, fontFamily: "Iosevka"))
]),
SizedBox(
height: 20,
),
Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [
TextButton(
onPressed: () {
LineFile doc = Provider.of<LineFile>(context);
newFile(doc);
},
child: Text("New", style: TextStyle(fontSize: 10.0, color: foreground, fontWeight: FontWeight.bold, fontFamily: "Iosevka"))),
TextButton(
onPressed: () {
LineFile doc = Provider.of<LineFile>(context);
openFile(doc);
},
child: Text("Open", style: TextStyle(fontSize: 10.0, color: foreground, fontWeight: FontWeight.bold, fontFamily: "Iosevka"))),
TextButton(
onPressed: () {
exit(0);
},
child: Text("Exit", style: TextStyle(fontSize: 10.0, color: foreground, fontWeight: FontWeight.bold, fontFamily: "Iosevka"))),
]),
Divider(
color: foreground,
)
])),
footer: Padding(padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 10.0), child: Divider(color: foreground, thickness: 4.0)),
onReorder: (int oldIndex, int newIndex) {
var doc = Provider.of<LineFile>(context, listen: false);
var startLine = sections[oldIndex].lineNumber;
var endLine = doc.lines();
for (var index = oldIndex + 1; index < sections.length; index++) {
print("${sections[oldIndex].level} > ${sections[index].level}");
if (sections[oldIndex].level < sections[index].level) {
// sub section...skipping
} else {
endLine = sections[index].lineNumber;
break;
}
}
var endLine = doc.lines();
for (var index = oldIndex + 1; index < sections.length; index++) {
print("${sections[oldIndex].level} > ${sections[index].level}");
if (sections[oldIndex].level < sections[index].level) {
// sub section...skipping
} else {
endLine = sections[index].lineNumber;
break;
}
}
var newLine = newIndex >= sections.length ? doc.lines() : sections[newIndex].lineNumber;
var newLine = newIndex >= sections.length ? doc.lines() : sections[newIndex].lineNumber;
doc.moveLines(startLine, endLine, newLine);
},
buildDefaultDragHandles: false,
itemBuilder: (BuildContext context, int index) {
return ListTile(
key: ValueKey(index),
tileColor: tabs,
title: Text(
sections[index].title.padLeft(sections[index].title.length + sections[index].level - 1, ' '),
style: TextStyle(color: foreground, fontSize: 14.0 - sections[index].level, fontFamily: "Iosevka"),
),
onTap: () {
Provider.of<LineFile>(context, listen: false).scrollController.jumpTo(index: sections[index].lineNumber - 1);
},
trailing: ReorderableDragStartListener(
index: index,
child: Icon(
Icons.drag_handle,
color: foreground,
),
),
);
},
itemCount: sections.length,
));
doc.moveLines(startLine, endLine, newLine);
},
buildDefaultDragHandles: false,
itemBuilder: (BuildContext context, int index) {
return ListTile(
key: ValueKey(index),
tileColor: tabs,
title: Text(
sections[index].title.padLeft(sections[index].title.length + sections[index].level - 1, ' '),
style: TextStyle(color: foreground, fontSize: 14.0 - sections[index].level, fontFamily: "Iosevka"),
),
onTap: () {
Provider.of<LineFile>(context, listen: false).scrollController.jumpTo(index: sections[index].lineNumber - 1);
},
trailing: ReorderableDragStartListener(
index: index,
child: Icon(
Icons.drag_handle,
color: foreground,
),
),
);
},
itemCount: sections.length,
));
}));
}
}

View File

@ -0,0 +1,76 @@
import 'dart:collection';
import 'dart:io';
import 'package:flutter/foundation.dart';
class SpellChecker {
HashMap<String, HashSet<String>> dictionary;
HashSet<String> known;
SpellChecker(this.dictionary, this.known);
static SpellChecker load(String dictPath) {
HashMap<String, HashSet<String>> dictionary = HashMap();
HashSet<String> known = HashSet();
File dict = File(dictPath);
print("building dictionary...");
dict.readAsLinesSync().forEach((word) {
var lower = word.toLowerCase().trim();
dictionary.putIfAbsent(lower, () => HashSet.from([lower]));
known.add(lower);
mutations1(lower).forEach((element) {
dictionary.putIfAbsent(element, () => HashSet());
dictionary[element]!.add(lower);
});
});
print("builing dictionary...${dictionary.length}");
return SpellChecker(dictionary, known);
}
HashSet<String>? candidates(String word) {
return dictionary[word];
}
}
HashSet<String> mutations1(String word) {
// "All edits that are one edit away from `word`."
var letters = 'abcdefghijklmnopqrstuvwxyz';
HashSet<String> mutations = HashSet();
// // delete a letter
for (int i = 0; i < word.length; i++) {
mutations.add(word.substring(0, i) + word.substring(i + 1));
}
// transpositions
for (int i = 0; i < word.length - 1; i++) {
mutations.add(word.substring(0, i) + word.substring(i + 1, i + 2) + word.substring(i, i + 1) + word.substring(i + 2));
}
// insert a letter
for (int i = 0; i < word.length; i++) {
for (var c = 0; c < 25; c++) {
mutations.add(word.substring(0, i) + letters.substring(c, c + 1) + word.substring(i));
}
}
// replace a letter
for (int i = 0; i < word.length; i++) {
for (var c = 0; c < 25; c++) {
mutations.add(word.substring(0, i) + letters.substring(c, c + 1) + word.substring(i + 1));
}
}
return mutations;
}
HashSet<String> mutations2(String word) {
HashSet<String> mutations = HashSet();
for (var e2 in mutations1(word)) {
mutations.addAll(mutations1(e2));
}
return mutations;
}

View File

@ -6,6 +6,8 @@ import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:subwrite/theme.dart';
import 'file.dart';
import 'ghostsense.dart';
import 'highlighter.dart';
import 'line_info.dart';
class View extends StatefulWidget {
@ -31,6 +33,9 @@ class _View extends State<View> with SingleTickerProviderStateMixin {
super.dispose();
}
double x = 0.0;
double y = 0.0;
@override
Widget build(BuildContext context) {
LineFile doc = Provider.of<LineFile>(context);
@ -43,15 +48,7 @@ class _View extends State<View> with SingleTickerProviderStateMixin {
children: [
ElevatedButton.icon(
onPressed: () async {
String? result = await FilePicker.platform.saveFile();
if (result != null) {
File(result).createSync(recursive: false);
doc.openFile(result, 0);
doc.focus.requestFocus();
} else {
// User canceled the picker
}
newFile(doc);
},
icon: Icon(Icons.create, color: foreground),
label: const Text("New Document"),
@ -61,16 +58,7 @@ class _View extends State<View> with SingleTickerProviderStateMixin {
),
ElevatedButton.icon(
onPressed: () async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
allowMultiple: false,
type: FileType.custom,
allowedExtensions: ['md', 'txt'],
);
if (result != null) {
doc.openFile(result.files.single.path!, 0);
} else {
// User canceled the picker
}
openFile(doc);
},
icon: Icon(Icons.file_open, color: foreground),
label: const Text("Open Document"),
@ -79,21 +67,101 @@ class _View extends State<View> with SingleTickerProviderStateMixin {
),
);
} else {
return ScrollablePositionedList.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemScrollController: doc.scrollController,
itemCount: doc.lines(),
padding: EdgeInsets.zero,
minCacheExtent: 10,
addRepaintBoundaries: true,
itemBuilder: (BuildContext bcontext, int index) {
return ChangeNotifierProvider.value(
value: doc.backingLines[index],
builder: (lcontext, lineinfo) {
return Provider.of<LineInfo>(lcontext).build(context, _animationController);
});
});
return MouseRegion(
onHover: (PointerEvent details) {
if (!doc.showSuggestions) {
setState(() {
x = details.localPosition.dx;
y = details.localPosition.dy;
});
}
},
child: Stack(
children: [
ScrollablePositionedList.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemScrollController: doc.scrollController,
itemCount: doc.lines(),
padding: EdgeInsets.zero,
minCacheExtent: 10,
addRepaintBoundaries: true,
itemBuilder: (BuildContext bcontext, int index) {
return ChangeNotifierProvider.value(
value: doc.backingLines[index],
builder: (lcontext, lineinfo) {
return Provider.of<LineInfo>(lcontext).build(context, _animationController);
});
}),
Visibility(
visible: doc.showSuggestions,
maintainInteractivity: false,
maintainState: false,
child: Positioned(
width: 400,
height: 200,
left: x,
top: y,
child: Container(width: 300, decoration: BoxDecoration(color: sidebarAlt, border: Border.all(color: sidebar, width: 1.0)), child: Ghostsense(doc.suggestions))),
)
],
));
}
}
}
Future<void> openFile(LineFile doc) async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
allowMultiple: false,
type: FileType.custom,
allowedExtensions: ['md', 'txt'],
);
if (result != null) {
doc.openFile(result.files.single.path!, 0);
} else {
// User canceled the picker
}
}
Future<void> newFile(LineFile doc) async {
String? result = await FilePicker.platform.saveFile();
if (result != null) {
File(result).createSync(recursive: false);
doc.openFile(result, 0);
doc.focus.requestFocus();
} else {
// User canceled the picker
}
}
class Suggestion {
String classType;
String name;
Suggestion(
this.classType,
this.name,
);
@override
bool operator ==(Object other) {
return other is Suggestion && hashCode == other.hashCode;
}
@override
int get hashCode => classType.hashCode + name.hashCode;
Widget getWidget() {
var color = function;
switch (classType) {
case "spelling":
color = header;
break;
}
return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
RichText(text: TextSpan(text: name, style: TextStyle(color: color, fontFamily: "Iosevka", fontSize: 11.0, fontWeight: FontWeight.bold))),
RichText(text: TextSpan(text: "dictionary", style: TextStyle(color: foreground, fontFamily: "Iosevka", fontSize: 10.0))),
]);
}
}

View File

@ -49,7 +49,7 @@ static void my_application_activate(GApplication* application) {
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
gtk_window_set_icon_from_file(window, "./subwrite-icon.png", NULL);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);

View File

@ -62,6 +62,8 @@ flutter:
# the material Icons class.
uses-material-design: true
assets:
- assets/subwrite-logo.png
fonts:
- family: Iosevka

BIN
subwrite-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
subwrite-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

16
test/dict_test.dart Normal file
View File

@ -0,0 +1,16 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:subwrite/spellcheck/spellchecker.dart';
void main() {
// Define the test
test("test dictionary",(){
var dict = SpellChecker("dicts/english.txt");
for (var candidate in dict.candidates("wella")) {
print("Candidate: $candidate");
}
});
}