diff --git a/lib/file.dart b/lib/file.dart index c5da717..7006703 100644 --- a/lib/file.dart +++ b/lib/file.dart @@ -225,6 +225,7 @@ class LineFile extends ChangeNotifier { cursor.publishCursor(backingLines); } } + parse(); return KeyEventResult.handled; } diff --git a/lib/highlighter.dart b/lib/highlighter.dart index 283ef59..4a91171 100644 --- a/lib/highlighter.dart +++ b/lib/highlighter.dart @@ -3,9 +3,7 @@ import 'package:subwrite/sourcelink.dart'; import 'package:subwrite/theme.dart'; import 'annotation.dart'; - -double fontSize = 14; -double gutterFontSize = 12; +import 'line_info.dart'; class LineDecoration { int start = 0; @@ -22,7 +20,8 @@ class LineDecoration { class Highlighter { Highlighter() {} - List run(BuildContext context, String text, int line, bool highlightRisk, TextStyle style, List? insights, int? cursorStart, int? cursorEnd, bool lineSelected) { + List run(BuildContext context, String text, int line, bool highlightRisk, TextStyle style, List? insights, int? cursorStart, int? cursorEnd, bool lineSelected, + int maxCharsBeforeWrapping) { TextStyle defaultStyle = style.copyWith(fontFamily: 'Iosevka', fontSize: fontSize, color: foreground, height: 1.2); List res = []; List decors = []; @@ -105,11 +104,26 @@ class Highlighter { } if (current == " ") { - res.add(TextSpan(text: "_", style: style.copyWith(color: style.backgroundColor), semanticsLabel: " ", mouseCursor: SystemMouseCursors.text)); + // NOTE: This is really annoying + // Ideally we could put a non breaking space here and force richtext to just do the thing we want and fill all available space... + // however richtext really doesn't want to do that... + // if this is a space of any kind (even full-width non-breaking spaces), richtext will invisibly drop the space... + // the space happens if this is a size block. + // HOWEVER: if this is an printable char other than a space the algorithm works as expected....EXCEPT + // if we try to change the color to hide that fact the renderer freaks out on lines with more than ~87-90 words and will cause + // the line to shake... + + // so we first add the space... + res.add(TextSpan(text: ' \u200B', style: style, mouseCursor: SystemMouseCursors.text)); + // and then if we are going to drop the space... + if (i % maxCharsBeforeWrapping == 0) { + // add another space...that we definitely won't drop... + res.add(TextSpan(text: '_\u200B', style: style.copyWith(color: style.backgroundColor), mouseCursor: SystemMouseCursors.text)); + } } else { // we need to add a '\ufeff' (Zero Width No-Break Space) here to avoid RichText doing ridiculous // breaking logic with hyphens / slashes etc. - res.add(TextSpan(text: '$current\ufeff', style: style, mouseCursor: SystemMouseCursors.text)); + res.add(TextSpan(text: '$current\u200B', style: style, mouseCursor: SystemMouseCursors.text)); } } return res; diff --git a/lib/line_info.dart b/lib/line_info.dart index 6c82c4b..e0efecf 100644 --- a/lib/line_info.dart +++ b/lib/line_info.dart @@ -10,9 +10,13 @@ import 'cursor.dart'; import 'file.dart'; import 'highlighter.dart'; -double gutterWidth = 64; -double rightMargin = 32; -double leftMargin = 5.0; +const double fontSize = 14; +const double gutterFontSize = 12; +const double gutterWidth = 64; +const double rightMargin = 32; +const double leftMargin = 5.0; +const charWidth = (fontSize / 2.0); +const lineHeight = (fontSize * 1.2); class LineInfo extends ChangeNotifier { String path; @@ -31,14 +35,40 @@ class LineInfo extends ChangeNotifier { var backgroundStyle = TextStyle(backgroundColor: cursorStart != null ? sidebarHighlight : background); - Highlighter hl = Highlighter(); - List spans = hl.run(context, text, lineNumber, highlightRisk, backgroundStyle, annotations, cursorStart, cursorEnd, lineSelected); - final gutterStyle = TextStyle(fontFamily: 'Iosevka', decoration: null, decorationColor: null, fontSize: gutterFontSize, color: comment, backgroundColor: sidebarAlt, height: 1.0); + var sidebarContent = List.empty(growable: true); + sidebarContent.add(Text('$lineNumber'.padLeft(5), style: gutterStyle)); + + var icons = annotations.where((element) => element.annotationType == AnnotationType.note).map((e) { + highlightRisk = true; + return Padding(padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.0), child: Tooltip(message: e.description!, child: e.getIcon(15.0))); + }).toList(); + + sidebarContent.addAll(icons); + + icons = annotations.where((element) => element.annotationType == AnnotationType.todo).map((e) { + highlightRisk = true; + return Padding(padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.0), child: e.getIcon(15.0)); + }).toList(); + + sidebarContent.addAll(icons); + return LayoutBuilder( builder: (context, constraints) { + var usableSize = (constraints.maxWidth - gutterWidth - rightMargin - leftMargin).floorToDouble(); + var charWidth = (fontSize / 2.0); + var lineHeight = (fontSize * 1.2); + var maxCharsBeforeWrapping = (usableSize / charWidth).floor(); + + Highlighter hl = Highlighter(); + List spans = hl.run(context, text, lineNumber, highlightRisk, backgroundStyle, annotations, cursorStart, cursorEnd, lineSelected, maxCharsBeforeWrapping); + var highlightedLine = RichText( + softWrap: true, + textWidthBasis: TextWidthBasis.longestLine, + strutStyle: StrutStyle.disabled, + textAlign: TextAlign.left, text: TextSpan( children: spans, style: backgroundStyle, @@ -47,31 +77,12 @@ class LineInfo extends ChangeNotifier { List stackElements = List.empty(growable: true); - var charWidth = (fontSize / 2.0); - var lineHeight = (fontSize * 1.2); - var usableSize = (constraints.maxWidth.floorToDouble() - gutterWidth - rightMargin - leftMargin).floorToDouble(); + ; - var sidebarContent = List.empty(growable: true); - sidebarContent.add(Text('$lineNumber'.padLeft(5), style: gutterStyle)); - - var icons = annotations.where((element) => element.annotationType == AnnotationType.note).map((e) { - highlightRisk = true; - return Padding(padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.0), child: Tooltip(message: e.description!, child: e.getIcon(15.0))); - }).toList(); - - sidebarContent.addAll(icons); - - icons = annotations.where((element) => element.annotationType == AnnotationType.todo).map((e) { - highlightRisk = true; - return Padding(padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.0), child: e.getIcon(15.0)); - }).toList(); - - sidebarContent.addAll(icons); - - var idealHeight = (lineHeight * (1.0 + ((text.length * charWidth) / usableSize).floor())).ceilToDouble(); + var idealHeight = (lineHeight * (1.0 + ((text.length * charWidth) / usableSize).floorToDouble())).ceilToDouble(); var highlightedLineContainer = Container( - padding: EdgeInsets.only(right: 0, top: 0, bottom: 0, left: 0), + padding: EdgeInsets.zero, margin: EdgeInsets.only(right: rightMargin, top: 0, bottom: 0, left: leftMargin), decoration: BoxDecoration( color: backgroundStyle.backgroundColor, @@ -94,8 +105,6 @@ class LineInfo extends ChangeNotifier { } var cursorOverlay = Stack(children: stackElements); - //print("rebuilding line $lineNumber $idealHeight"); - // print("$charWidth $lineHeight $usableSize $idealHeight ${constraints.maxWidth}"); return Listener( onPointerDown: (event) { @@ -165,13 +174,14 @@ class LineInfo extends ChangeNotifier { width: (usableSize + gutterWidth + rightMargin + leftMargin).floorToDouble(), child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.zero, decoration: BoxDecoration(color: sidebarAlt), + // row can only have gutterWidth+margin wide width: (gutterWidth - 2).floorToDouble(), height: idealHeight, + margin: const EdgeInsets.only(right: 1, top: 0, bottom: 0, left: 0), + padding: EdgeInsets.zero, alignment: Alignment.centerLeft, - margin: EdgeInsets.only(right: 1, top: 0, bottom: 0, left: 0), - child: Row(mainAxisAlignment: MainAxisAlignment.start, children: sidebarContent)), + child: Row(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: sidebarContent)), cursorOverlay, ])))); }, diff --git a/lib/outline.dart b/lib/outline.dart index 1552acd..1122a1e 100644 --- a/lib/outline.dart +++ b/lib/outline.dart @@ -52,13 +52,13 @@ class _Outline extends State { Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton( onPressed: () { - LineFile doc = Provider.of(context); + LineFile doc = Provider.of(context, listen: false); newFile(doc); }, child: Text("New", style: TextStyle(fontSize: 10.0, color: foreground, fontWeight: FontWeight.bold, fontFamily: "Iosevka"))), TextButton( onPressed: () { - LineFile doc = Provider.of(context); + LineFile doc = Provider.of(context, listen: false); openFile(doc); }, child: Text("Open", style: TextStyle(fontSize: 10.0, color: foreground, fontWeight: FontWeight.bold, fontFamily: "Iosevka"))), diff --git a/lib/view.dart b/lib/view.dart index 06131cf..6fa4aca 100644 --- a/lib/view.dart +++ b/lib/view.dart @@ -77,6 +77,7 @@ class _View extends State with SingleTickerProviderStateMixin { } }, child: Stack( + fit: StackFit.expand, children: [ ScrollablePositionedList.builder( physics: const AlwaysScrollableScrollPhysics(), @@ -84,7 +85,6 @@ class _View extends State with SingleTickerProviderStateMixin { itemCount: doc.lines(), padding: EdgeInsets.zero, minCacheExtent: 10, - addRepaintBoundaries: true, itemBuilder: (BuildContext bcontext, int index) { return ChangeNotifierProvider.value( value: doc.backingLines[index],