From 38fd616d4d226e723a4cdb2c39702c3676dd7f76 Mon Sep 17 00:00:00 2001 From: xJac0b Date: Tue, 19 Nov 2024 22:43:16 +0100 Subject: [PATCH 1/8] line numbers, sort CodeEditText fields --- example/lib/main.dart | 1 + lib/src/presentation/code_edit_text.dart | 103 +++++++++++++++++------ 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index fb51380..7fd161c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -56,6 +56,7 @@ class _MyAppState extends State { code: codeSnippet, controller: controller, showCursor: true, + enableLineNumbers: true, ), ), ], diff --git a/lib/src/presentation/code_edit_text.dart b/lib/src/presentation/code_edit_text.dart index 2374db7..bb50dc8 100644 --- a/lib/src/presentation/code_edit_text.dart +++ b/lib/src/presentation/code_edit_text.dart @@ -1,35 +1,60 @@ import 'package:flutter/material.dart'; +import 'package:kode_view/src/presentation/styles/text_styles.dart'; import 'package:kode_view/src/presentation/syntax_highlighting_controller.dart'; import 'package:kode_view/src/presentation/text_selection_options.dart'; class CodeEditText extends StatefulWidget { const CodeEditText({ required this.code, + + // Core functionality this.controller, - this.maxLines, - this.options, - this.showCursor, - this.onTap, this.language, this.theme, this.textStyle, + this.maxLines, + + // Appearance this.decoration, + this.enableLineNumbers = false, + this.lineNumberColor, + this.lineNumberBackgroundColor, + + // Interaction + this.showCursor, + this.options, + this.onTap, + + // Debugging this.debug = false, + + // Flutter widget key super.key, }); + // Required final String code; + + // Core functionality + final SyntaxHighlightingController? controller; + final String? language; + final String? theme; + final TextStyle? textStyle; final int? maxLines; + + // Appearance + final InputDecoration? decoration; + final bool enableLineNumbers; + final Color? lineNumberColor; + final Color? lineNumberBackgroundColor; + + // Interaction final bool? showCursor; final TextSelectionOptions? options; - final InputDecoration? decoration; final GestureTapCallback? onTap; - final SyntaxHighlightingController? controller; - final bool debug; - final String? language; - final String? theme; - final TextStyle? textStyle; + // Debugging + final bool debug; @override State createState() => _CodeEditTextState(); @@ -65,20 +90,50 @@ class _CodeEditTextState extends State { return ValueListenableBuilder( valueListenable: _controller.textSpansNotifier, builder: (context, __, _) { - return TextField( - controller: _controller, - style: widget.textStyle, - onTap: widget.onTap, - minLines: 1, - maxLines: widget.maxLines, - contextMenuBuilder: widget.options != null - ? (context, editableTextState) => - widget.options!.toolbarOptions(context, editableTextState) - : null, - enableInteractiveSelection: widget.options != null, - showCursor: widget.showCursor ?? true, - scrollPhysics: const ClampingScrollPhysics(), - decoration: widget.decoration, + return Row( + children: [ + if (widget.enableLineNumbers) ...[ + Container( + color: widget.lineNumberBackgroundColor, + width: 40, + child: ListView.builder( + shrinkWrap: true, + itemCount: _controller.text.split('\n').length, + itemBuilder: (context, index) { + return Text( + '${index + 1}', + textAlign: TextAlign.right, + style: TextStyle( + color: widget.lineNumberColor ?? Colors.black54, + fontSize: widget.textStyle?.fontSize ?? + const TextStyles.code('').style!.fontSize, + ), + ); + }, + ), + ), + const SizedBox( + width: 4, + ), + ], + Expanded( + child: TextField( + controller: _controller, + style: widget.textStyle, + onTap: widget.onTap, + minLines: 1, + maxLines: widget.maxLines, + contextMenuBuilder: widget.options != null + ? (context, editableTextState) => widget.options! + .toolbarOptions(context, editableTextState) + : null, + enableInteractiveSelection: widget.options != null, + showCursor: widget.showCursor ?? true, + scrollPhysics: const ClampingScrollPhysics(), + decoration: widget.decoration, + ), + ), + ], ); }, ); From dffde41dabcbec9c3721e4f830a8adc9f9a62b38 Mon Sep 17 00:00:00 2001 From: xJac0b Date: Tue, 19 Nov 2024 23:02:56 +0100 Subject: [PATCH 2/8] LineNumbersWrapper, add line numbers to CodeTextView --- example/lib/main.dart | 1 + lib/src/presentation/code_edit_text.dart | 71 +++++++------------ lib/src/presentation/code_text_view.dart | 34 +++++---- .../presentation/line_numbers_wrapper.dart | 54 ++++++++++++++ 4 files changed, 100 insertions(+), 60 deletions(-) create mode 100644 lib/src/presentation/line_numbers_wrapper.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 7fd161c..7d83b9b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -45,6 +45,7 @@ class _MyAppState extends State { selectAll: true, share: true, ), + enableLineNumbers: true, ), ), const SizedBox(height: 24), diff --git a/lib/src/presentation/code_edit_text.dart b/lib/src/presentation/code_edit_text.dart index bb50dc8..b1eb9c3 100644 --- a/lib/src/presentation/code_edit_text.dart +++ b/lib/src/presentation/code_edit_text.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:kode_view/src/presentation/styles/text_styles.dart'; +import 'package:kode_view/src/presentation/line_numbers_wrapper.dart'; import 'package:kode_view/src/presentation/syntax_highlighting_controller.dart'; import 'package:kode_view/src/presentation/text_selection_options.dart'; @@ -67,8 +67,11 @@ class _CodeEditTextState extends State { void initState() { super.initState(); _controller = widget.controller ?? - SyntaxHighlightingController(text: widget.code, debug: widget.debug, - )..addListener(() { + SyntaxHighlightingController( + text: widget.code, + debug: widget.debug, + ) + ..addListener(() { _controller.updateSyntaxHighlighting( code: _controller.text, language: widget.language, @@ -90,50 +93,24 @@ class _CodeEditTextState extends State { return ValueListenableBuilder( valueListenable: _controller.textSpansNotifier, builder: (context, __, _) { - return Row( - children: [ - if (widget.enableLineNumbers) ...[ - Container( - color: widget.lineNumberBackgroundColor, - width: 40, - child: ListView.builder( - shrinkWrap: true, - itemCount: _controller.text.split('\n').length, - itemBuilder: (context, index) { - return Text( - '${index + 1}', - textAlign: TextAlign.right, - style: TextStyle( - color: widget.lineNumberColor ?? Colors.black54, - fontSize: widget.textStyle?.fontSize ?? - const TextStyles.code('').style!.fontSize, - ), - ); - }, - ), - ), - const SizedBox( - width: 4, - ), - ], - Expanded( - child: TextField( - controller: _controller, - style: widget.textStyle, - onTap: widget.onTap, - minLines: 1, - maxLines: widget.maxLines, - contextMenuBuilder: widget.options != null - ? (context, editableTextState) => widget.options! - .toolbarOptions(context, editableTextState) - : null, - enableInteractiveSelection: widget.options != null, - showCursor: widget.showCursor ?? true, - scrollPhysics: const ClampingScrollPhysics(), - decoration: widget.decoration, - ), - ), - ], + return LineNumbersWrapper( + enableLineNumbers: widget.enableLineNumbers, + linesNumber: _controller.text.split('\n').length, + child: TextField( + controller: _controller, + style: widget.textStyle, + onTap: widget.onTap, + minLines: 1, + maxLines: widget.maxLines, + contextMenuBuilder: widget.options != null + ? (context, editableTextState) => + widget.options!.toolbarOptions(context, editableTextState) + : null, + enableInteractiveSelection: widget.options != null, + showCursor: widget.showCursor ?? true, + scrollPhysics: const ClampingScrollPhysics(), + decoration: widget.decoration, + ), ); }, ); diff --git a/lib/src/presentation/code_text_view.dart b/lib/src/presentation/code_text_view.dart index fad3674..aec96e9 100644 --- a/lib/src/presentation/code_text_view.dart +++ b/lib/src/presentation/code_text_view.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:highlights_plugin/highlights_plugin.dart'; +import 'package:kode_view/src/presentation/line_numbers_wrapper.dart'; import 'package:kode_view/src/presentation/styles/text_styles.dart'; import 'package:kode_view/src/presentation/text_selection_options.dart'; import 'package:kode_view/src/utils/extensions/collection_extensions.dart'; @@ -17,6 +18,7 @@ class CodeTextView extends StatelessWidget { this.language, this.theme, this.textStyle, + this.enableLineNumbers = false, super.key, }); @@ -26,6 +28,7 @@ class CodeTextView extends StatelessWidget { final bool? showCursor; final TextSelectionOptions? options; final GestureTapCallback? onTap; + final bool enableLineNumbers; final String? language; final String? theme; @@ -39,6 +42,7 @@ class CodeTextView extends StatelessWidget { this.language, this.theme, this.textStyle, + this.enableLineNumbers = false, super.key, }) : maxLines = 5; @@ -50,19 +54,23 @@ class CodeTextView extends StatelessWidget { initialData: const [], future: _highlights(maxLinesOrAll), builder: (_, value) { - return SelectableText.rich( - TextSpan(children: value.requireData), - style: textStyle ?? TextStyles.code(code).style!, - minLines: 1, - maxLines: maxLinesOrAll, - onTap: () {}, - contextMenuBuilder: options != null - ? (context, editableTextState) => - options!.toolbarOptions(context, editableTextState) - : null, - enableInteractiveSelection: options != null, - showCursor: showCursor ?? false, - scrollPhysics: const ClampingScrollPhysics(), + return LineNumbersWrapper( + enableLineNumbers: enableLineNumbers, + linesNumber: maxLinesOrAll, + child: SelectableText.rich( + TextSpan(children: value.requireData), + style: textStyle ?? TextStyles.code(code).style!, + minLines: 1, + maxLines: maxLinesOrAll, + onTap: () {}, + contextMenuBuilder: options != null + ? (context, editableTextState) => + options!.toolbarOptions(context, editableTextState) + : null, + enableInteractiveSelection: options != null, + showCursor: showCursor ?? false, + scrollPhysics: const ClampingScrollPhysics(), + ), ); }, ); diff --git a/lib/src/presentation/line_numbers_wrapper.dart b/lib/src/presentation/line_numbers_wrapper.dart new file mode 100644 index 0000000..d72c882 --- /dev/null +++ b/lib/src/presentation/line_numbers_wrapper.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:kode_view/src/presentation/styles/text_styles.dart'; + +class LineNumbersWrapper extends StatelessWidget { + const LineNumbersWrapper({ + required this.enableLineNumbers, + required this.linesNumber, + required this.child, + this.textStyle, + this.lineNumberBackgroundColor, + this.lineNumberColor, + super.key, + }); + final bool enableLineNumbers; + final int linesNumber; + final Color? lineNumberBackgroundColor; + final Color? lineNumberColor; + final TextStyle? textStyle; + final Widget child; + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (enableLineNumbers) ...[ + Container( + color: lineNumberBackgroundColor, + width: 40, + child: ListView.builder( + shrinkWrap: true, + itemCount: linesNumber, + itemBuilder: (context, index) { + return Text( + '${index + 1}', + textAlign: TextAlign.right, + style: TextStyle( + color: lineNumberColor ?? Colors.black54, + fontSize: textStyle?.fontSize ?? + const TextStyles.code('').style!.fontSize, + ), + ); + }, + ), + ), + const SizedBox( + width: 4, + ), + ], + Expanded( + child: child, + ), + ], + ); + } +} From d03edd4fca8397d8d58cf0f3297cbb46da210ad8 Mon Sep 17 00:00:00 2001 From: xJac0b Date: Thu, 21 Nov 2024 11:30:38 +0100 Subject: [PATCH 3/8] remove unnecessary text styles, remove field comments --- example/lib/main.dart | 2 +- lib/src/presentation/code_edit_text.dart | 27 ++---- lib/src/presentation/code_text_view.dart | 5 +- lib/src/presentation/styles/text_styles.dart | 92 +------------------ .../syntax_highlighting_controller.dart | 7 +- .../extensions/collection_extensions.dart | 4 +- 6 files changed, 19 insertions(+), 118 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 7d83b9b..7f206e8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -57,7 +57,7 @@ class _MyAppState extends State { code: codeSnippet, controller: controller, showCursor: true, - enableLineNumbers: true, + showLineNumbers: true, ), ), ], diff --git a/lib/src/presentation/code_edit_text.dart b/lib/src/presentation/code_edit_text.dart index b1eb9c3..1d4e2dc 100644 --- a/lib/src/presentation/code_edit_text.dart +++ b/lib/src/presentation/code_edit_text.dart @@ -1,59 +1,45 @@ import 'package:flutter/material.dart'; import 'package:kode_view/src/presentation/line_numbers_wrapper.dart'; +import 'package:kode_view/src/presentation/styles/text_styles.dart'; import 'package:kode_view/src/presentation/syntax_highlighting_controller.dart'; import 'package:kode_view/src/presentation/text_selection_options.dart'; class CodeEditText extends StatefulWidget { const CodeEditText({ required this.code, - - // Core functionality this.controller, this.language, this.theme, - this.textStyle, + this.textStyle = const TextStyles.code(), this.maxLines, - - // Appearance this.decoration, - this.enableLineNumbers = false, + this.showLineNumbers = false, this.lineNumberColor, this.lineNumberBackgroundColor, - - // Interaction this.showCursor, this.options, this.onTap, - - // Debugging this.debug = false, - - // Flutter widget key super.key, }); - // Required final String code; - // Core functionality final SyntaxHighlightingController? controller; final String? language; final String? theme; - final TextStyle? textStyle; + final TextStyle textStyle; final int? maxLines; - // Appearance final InputDecoration? decoration; - final bool enableLineNumbers; + final bool showLineNumbers; final Color? lineNumberColor; final Color? lineNumberBackgroundColor; - // Interaction final bool? showCursor; final TextSelectionOptions? options; final GestureTapCallback? onTap; - // Debugging final bool debug; @override @@ -94,8 +80,9 @@ class _CodeEditTextState extends State { valueListenable: _controller.textSpansNotifier, builder: (context, __, _) { return LineNumbersWrapper( - enableLineNumbers: widget.enableLineNumbers, + enableLineNumbers: widget.showLineNumbers, linesNumber: _controller.text.split('\n').length, + textStyle: widget.textStyle, child: TextField( controller: _controller, style: widget.textStyle, diff --git a/lib/src/presentation/code_text_view.dart b/lib/src/presentation/code_text_view.dart index aec96e9..149e88e 100644 --- a/lib/src/presentation/code_text_view.dart +++ b/lib/src/presentation/code_text_view.dart @@ -57,9 +57,10 @@ class CodeTextView extends StatelessWidget { return LineNumbersWrapper( enableLineNumbers: enableLineNumbers, linesNumber: maxLinesOrAll, + textStyle: textStyle, child: SelectableText.rich( TextSpan(children: value.requireData), - style: textStyle ?? TextStyles.code(code).style!, + style: textStyle ?? const TextStyles.code(), minLines: 1, maxLines: maxLinesOrAll, onTap: () {}, @@ -85,7 +86,7 @@ class CodeTextView extends StatelessWidget { ); return highlights.toSpans( code.lines(maxLinesOrAll), - textStyle ?? TextStyles.code(code).style!, + textStyle ?? const TextStyles.code(), ); } } diff --git a/lib/src/presentation/styles/text_styles.dart b/lib/src/presentation/styles/text_styles.dart index ee4d9be..62ff5ec 100644 --- a/lib/src/presentation/styles/text_styles.dart +++ b/lib/src/presentation/styles/text_styles.dart @@ -1,93 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:kode_view/src/presentation/styles/color_styles.dart'; -class TextStyles extends Text { - final String text; - - const TextStyles.title(this.text, {Key? key}) - : super( - text, - key: key, - style: const TextStyle(fontSize: 16), - ); - - const TextStyles.code(this.text, {Key? key}) - : super( - text, - key: key, - style: const TextStyle( - fontSize: 16, - fontStyle: FontStyle.normal, - ), - ); - - TextStyles.regular(this.text, {Key? key, Color? color}) - : super( - text, - key: key, - style: TextStyle(color: color), - ); - - TextStyles.bold(this.text, {Key? key, Color? color}) - : super( - text, - key: key, - style: TextStyle( - color: color, - fontWeight: FontWeight.w600, - ), - ); - - const TextStyles.secondary(this.text, {Key? key}) - : super( - text, - key: key, - style: const TextStyle(color: Colors.grey), - ); - - const TextStyles.secondaryBold(this.text, {Key? key}) - : super( - text, - key: key, - style: const TextStyle( - color: Colors.grey, - fontWeight: FontWeight.bold, - ), - ); - - const TextStyles.label(this.text, {Key? key}) - : super( - text, - key: key, - style: const TextStyle(fontSize: 12, color: Colors.grey), - ); - - const TextStyles.helper(this.text, {Key? key}) - : super( - text, - key: key, - style: const TextStyle(fontSize: 10, color: Colors.grey), - ); - - TextStyles.appLogo(this.text, {Key? key}) - : super( - text, - key: key, - style: TextStyle( - fontFamily: 'Kanit', - fontSize: 24.0, - color: ColorStyles.accent(), - ), - ); - - const TextStyles.appBarLogo(this.text, {Key? key}) +class TextStyles extends TextStyle { + const TextStyles.code({Key? key}) : super( - text, - key: key, - style: const TextStyle( - fontFamily: 'Kanit', - fontSize: 18.0, - color: Colors.black, - ), + fontSize: 16, + fontStyle: FontStyle.normal, ); } diff --git a/lib/src/presentation/syntax_highlighting_controller.dart b/lib/src/presentation/syntax_highlighting_controller.dart index c5b3b99..8f927f4 100644 --- a/lib/src/presentation/syntax_highlighting_controller.dart +++ b/lib/src/presentation/syntax_highlighting_controller.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:highlights_plugin/highlights_plugin.dart'; -import 'package:kode_view/src/presentation/styles/text_styles.dart'; import 'package:kode_view/src/utils/extensions/collection_extensions.dart'; class SyntaxHighlightingController extends TextEditingController { @@ -17,9 +16,9 @@ class SyntaxHighlightingController extends TextEditingController { Future updateSyntaxHighlighting({ required String code, + required TextStyle textStyle, String? theme, String? language, - TextStyle? textStyle, }) async { try { final highlightedText = await _highlights( @@ -50,7 +49,7 @@ class SyntaxHighlightingController extends TextEditingController { required String code, required String? language, required String? theme, - required TextStyle? textStyle, + required TextStyle textStyle, }) async { final highlights = await HighlightsPlugin().getHighlights( code, @@ -60,7 +59,7 @@ class SyntaxHighlightingController extends TextEditingController { ); return highlights.toSpans( code, - textStyle ?? TextStyles.code(code).style!, + textStyle, ); } } diff --git a/lib/src/utils/extensions/collection_extensions.dart b/lib/src/utils/extensions/collection_extensions.dart index b1e821b..85b0dd8 100644 --- a/lib/src/utils/extensions/collection_extensions.dart +++ b/lib/src/utils/extensions/collection_extensions.dart @@ -2,7 +2,6 @@ import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:highlights_plugin/model/code_highlight.dart'; -import 'package:kode_view/src/presentation/styles/text_styles.dart'; import 'package:kode_view/src/utils/extensions/text_extensions.dart'; class TokenSpan with EquatableMixin { @@ -64,8 +63,7 @@ extension SyntaxSpanExtension on List { ); if (foundToken != null) { - style = - TextStyles.code(text).style!.copyWith(color: foundToken.color); + style = baseStyle.copyWith(color: foundToken.color); } return TextSpan(text: phrase, style: style); From 150d3661a83304b55fc498aabafde0a20f891add Mon Sep 17 00:00:00 2001 From: xJac0b Date: Thu, 21 Nov 2024 11:53:02 +0100 Subject: [PATCH 4/8] fix line number overflow, add line number text style --- lib/src/presentation/code_edit_text.dart | 2 +- lib/src/presentation/code_text_view.dart | 12 +++---- .../presentation/line_numbers_wrapper.dart | 31 ++++++++++++++----- lib/src/presentation/styles/text_styles.dart | 8 ++++- lib/src/utils/extensions/text_extensions.dart | 10 ++++++ 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/lib/src/presentation/code_edit_text.dart b/lib/src/presentation/code_edit_text.dart index 1d4e2dc..a0e8110 100644 --- a/lib/src/presentation/code_edit_text.dart +++ b/lib/src/presentation/code_edit_text.dart @@ -82,7 +82,7 @@ class _CodeEditTextState extends State { return LineNumbersWrapper( enableLineNumbers: widget.showLineNumbers, linesNumber: _controller.text.split('\n').length, - textStyle: widget.textStyle, + fontSize: widget.textStyle.fontSize, child: TextField( controller: _controller, style: widget.textStyle, diff --git a/lib/src/presentation/code_text_view.dart b/lib/src/presentation/code_text_view.dart index 149e88e..6525327 100644 --- a/lib/src/presentation/code_text_view.dart +++ b/lib/src/presentation/code_text_view.dart @@ -17,7 +17,7 @@ class CodeTextView extends StatelessWidget { this.onTap, this.language, this.theme, - this.textStyle, + this.textStyle = const TextStyles.code(), this.enableLineNumbers = false, super.key, }); @@ -32,7 +32,7 @@ class CodeTextView extends StatelessWidget { final String? language; final String? theme; - final TextStyle? textStyle; + final TextStyle textStyle; const CodeTextView.preview({ required this.code, @@ -41,7 +41,7 @@ class CodeTextView extends StatelessWidget { this.showCursor, this.language, this.theme, - this.textStyle, + this.textStyle = const TextStyles.code(), this.enableLineNumbers = false, super.key, }) : maxLines = 5; @@ -57,10 +57,10 @@ class CodeTextView extends StatelessWidget { return LineNumbersWrapper( enableLineNumbers: enableLineNumbers, linesNumber: maxLinesOrAll, - textStyle: textStyle, + fontSize: textStyle.fontSize, child: SelectableText.rich( TextSpan(children: value.requireData), - style: textStyle ?? const TextStyles.code(), + style: textStyle, minLines: 1, maxLines: maxLinesOrAll, onTap: () {}, @@ -86,7 +86,7 @@ class CodeTextView extends StatelessWidget { ); return highlights.toSpans( code.lines(maxLinesOrAll), - textStyle ?? const TextStyles.code(), + textStyle, ); } } diff --git a/lib/src/presentation/line_numbers_wrapper.dart b/lib/src/presentation/line_numbers_wrapper.dart index d72c882..28bd120 100644 --- a/lib/src/presentation/line_numbers_wrapper.dart +++ b/lib/src/presentation/line_numbers_wrapper.dart @@ -1,12 +1,15 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:kode_view/src/presentation/styles/text_styles.dart'; +import 'package:kode_view/src/utils/extensions/text_extensions.dart'; class LineNumbersWrapper extends StatelessWidget { const LineNumbersWrapper({ required this.enableLineNumbers, required this.linesNumber, required this.child, - this.textStyle, + this.fontSize, this.lineNumberBackgroundColor, this.lineNumberColor, super.key, @@ -15,16 +18,31 @@ class LineNumbersWrapper extends StatelessWidget { final int linesNumber; final Color? lineNumberBackgroundColor; final Color? lineNumberColor; - final TextStyle? textStyle; + final double? fontSize; final Widget child; + @override Widget build(BuildContext context) { + double maxWidth = 0; + for (int i = 0; i < linesNumber; i++) { + String lineNumber = i.toString(); + double width = lineNumber.getTextWidth( + TextStyles.lineNumber( + color: lineNumberColor, + fontSize: fontSize, + ), + ); + maxWidth = max(maxWidth, width); + } + return Row( children: [ if (enableLineNumbers) ...[ Container( + constraints: BoxConstraints( + maxWidth: maxWidth + 10, + ), color: lineNumberBackgroundColor, - width: 40, child: ListView.builder( shrinkWrap: true, itemCount: linesNumber, @@ -32,10 +50,9 @@ class LineNumbersWrapper extends StatelessWidget { return Text( '${index + 1}', textAlign: TextAlign.right, - style: TextStyle( - color: lineNumberColor ?? Colors.black54, - fontSize: textStyle?.fontSize ?? - const TextStyles.code('').style!.fontSize, + style: TextStyles.lineNumber( + color: lineNumberColor, + fontSize: fontSize, ), ); }, diff --git a/lib/src/presentation/styles/text_styles.dart b/lib/src/presentation/styles/text_styles.dart index 62ff5ec..2ddd790 100644 --- a/lib/src/presentation/styles/text_styles.dart +++ b/lib/src/presentation/styles/text_styles.dart @@ -1,9 +1,15 @@ import 'package:flutter/material.dart'; class TextStyles extends TextStyle { - const TextStyles.code({Key? key}) + const TextStyles.code() : super( fontSize: 16, fontStyle: FontStyle.normal, ); + const TextStyles.lineNumber({Color? color, double? fontSize}) + : super( + color: Colors.black54, + fontSize: fontSize ?? 16, + fontStyle: FontStyle.normal, + ); } diff --git a/lib/src/utils/extensions/text_extensions.dart b/lib/src/utils/extensions/text_extensions.dart index f26b8c8..f7606eb 100644 --- a/lib/src/utils/extensions/text_extensions.dart +++ b/lib/src/utils/extensions/text_extensions.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:flutter/material.dart'; + extension TextExtensions on String { String get route => '/$this'; @@ -46,4 +48,12 @@ extension TextExtensions on String { final split = const LineSplitter().convert(this).take(count); return split.join('\n'); } + + double getTextWidth(TextStyle? style) { + final textPainter = TextPainter( + text: TextSpan(text: this, style: style), + textDirection: TextDirection.ltr, + )..layout(); + return textPainter.size.width; + } } From b6fde22c440a6db74e1c1eae585bab344115ce09 Mon Sep 17 00:00:00 2001 From: xJac0b Date: Thu, 21 Nov 2024 19:23:09 +0100 Subject: [PATCH 5/8] CodeEditText horizontal scroll --- lib/src/presentation/code_edit_text.dart | 33 ++++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/src/presentation/code_edit_text.dart b/lib/src/presentation/code_edit_text.dart index a0e8110..a4fd7c7 100644 --- a/lib/src/presentation/code_edit_text.dart +++ b/lib/src/presentation/code_edit_text.dart @@ -83,20 +83,25 @@ class _CodeEditTextState extends State { enableLineNumbers: widget.showLineNumbers, linesNumber: _controller.text.split('\n').length, fontSize: widget.textStyle.fontSize, - child: TextField( - controller: _controller, - style: widget.textStyle, - onTap: widget.onTap, - minLines: 1, - maxLines: widget.maxLines, - contextMenuBuilder: widget.options != null - ? (context, editableTextState) => - widget.options!.toolbarOptions(context, editableTextState) - : null, - enableInteractiveSelection: widget.options != null, - showCursor: widget.showCursor ?? true, - scrollPhysics: const ClampingScrollPhysics(), - decoration: widget.decoration, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: IntrinsicWidth( + child: TextField( + controller: _controller, + style: widget.textStyle, + onTap: widget.onTap, + minLines: 1, + maxLines: widget.maxLines, + contextMenuBuilder: widget.options != null + ? (context, editableTextState) => widget.options! + .toolbarOptions(context, editableTextState) + : null, + enableInteractiveSelection: widget.options != null, + showCursor: widget.showCursor ?? true, + scrollPhysics: const ClampingScrollPhysics(), + decoration: widget.decoration, + ), + ), ), ); }, From e0526ec6f94b8e0e5479fd08cbc06f9baabf7eec Mon Sep 17 00:00:00 2001 From: xJac0b Date: Fri, 22 Nov 2024 13:30:34 +0100 Subject: [PATCH 6/8] Calculate width and height of line numbers --- lib/src/presentation/code_edit_text.dart | 28 +++++++++- lib/src/presentation/code_text_view.dart | 54 ++++++++++++++----- .../presentation/line_numbers_wrapper.dart | 19 ++++--- lib/src/utils/extensions/text_extensions.dart | 5 +- 4 files changed, 84 insertions(+), 22 deletions(-) diff --git a/lib/src/presentation/code_edit_text.dart b/lib/src/presentation/code_edit_text.dart index a4fd7c7..b84d8ce 100644 --- a/lib/src/presentation/code_edit_text.dart +++ b/lib/src/presentation/code_edit_text.dart @@ -48,6 +48,10 @@ class CodeEditText extends StatefulWidget { class _CodeEditTextState extends State { late SyntaxHighlightingController _controller; + final ScrollController _lineNumbersScrollController = ScrollController(); + final ScrollController _textFieldScrollController = ScrollController(); + final GlobalKey _textFieldKey = GlobalKey(); + double _textFieldHeight = 0; @override void initState() { @@ -72,6 +76,21 @@ class _CodeEditTextState extends State { theme: widget.theme, textStyle: widget.textStyle, ); + + _textFieldScrollController.addListener(() { + if (_textFieldScrollController.offset != + _lineNumbersScrollController.offset) { + _lineNumbersScrollController.jumpTo(_textFieldScrollController.offset); + } + }); + } + + void _getTextFieldHeight() { + final RenderBox renderBox = + _textFieldKey.currentContext?.findRenderObject() as RenderBox; + setState(() { + _textFieldHeight = renderBox.size.height; + }); } @override @@ -79,14 +98,21 @@ class _CodeEditTextState extends State { return ValueListenableBuilder( valueListenable: _controller.textSpansNotifier, builder: (context, __, _) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _getTextFieldHeight(); + }); return LineNumbersWrapper( - enableLineNumbers: widget.showLineNumbers, + height: _textFieldHeight, + showLineNumbers: widget.showLineNumbers, + scrollController: _lineNumbersScrollController, linesNumber: _controller.text.split('\n').length, fontSize: widget.textStyle.fontSize, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: IntrinsicWidth( child: TextField( + key: _textFieldKey, + scrollController: _textFieldScrollController, controller: _controller, style: widget.textStyle, onTap: widget.onTap, diff --git a/lib/src/presentation/code_text_view.dart b/lib/src/presentation/code_text_view.dart index 6525327..2740b43 100644 --- a/lib/src/presentation/code_text_view.dart +++ b/lib/src/presentation/code_text_view.dart @@ -8,7 +8,7 @@ import 'package:kode_view/src/presentation/text_selection_options.dart'; import 'package:kode_view/src/utils/extensions/collection_extensions.dart'; import 'package:kode_view/src/utils/extensions/text_extensions.dart'; -class CodeTextView extends StatelessWidget { +class CodeTextView extends StatefulWidget { const CodeTextView({ required this.code, this.maxLines, @@ -23,6 +23,7 @@ class CodeTextView extends StatelessWidget { }); final splitter = const LineSplitter(); + final String code; final int? maxLines; final bool? showCursor; @@ -46,30 +47,55 @@ class CodeTextView extends StatelessWidget { super.key, }) : maxLines = 5; + @override + State createState() => _CodeTextViewState(); +} + +class _CodeTextViewState extends State { + final GlobalKey selectableTextkey = GlobalKey(); + double height = 0; + + void _getTextFieldHeight() { + final RenderBox renderBox = + selectableTextkey.currentContext?.findRenderObject() as RenderBox; + setState(() { + height = renderBox.size.height; + }); + } + @override Widget build(BuildContext context) { - final maxLinesOrAll = maxLines ?? splitter.convert(code).length; + final maxLinesOrAll = + widget.maxLines ?? widget.splitter.convert(widget.code).length; + final ScrollController controller = ScrollController(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _getTextFieldHeight(); + }); return FutureBuilder( initialData: const [], future: _highlights(maxLinesOrAll), builder: (_, value) { return LineNumbersWrapper( - enableLineNumbers: enableLineNumbers, + scrollController: controller, + showLineNumbers: widget.enableLineNumbers, + height: height, linesNumber: maxLinesOrAll, - fontSize: textStyle.fontSize, + fontSize: widget.textStyle.fontSize, child: SelectableText.rich( TextSpan(children: value.requireData), - style: textStyle, + key: selectableTextkey, + style: widget.textStyle, minLines: 1, maxLines: maxLinesOrAll, onTap: () {}, - contextMenuBuilder: options != null + contextMenuBuilder: widget.options != null ? (context, editableTextState) => - options!.toolbarOptions(context, editableTextState) + widget.options!.toolbarOptions(context, editableTextState) : null, - enableInteractiveSelection: options != null, - showCursor: showCursor ?? false, + enableInteractiveSelection: widget.options != null, + showCursor: widget.showCursor ?? false, scrollPhysics: const ClampingScrollPhysics(), ), ); @@ -79,14 +105,14 @@ class CodeTextView extends StatelessWidget { Future> _highlights(int maxLinesOrAll) async { final highlights = await HighlightsPlugin().getHighlights( - code, - language, - theme, + widget.code, + widget.language, + widget.theme, [], ); return highlights.toSpans( - code.lines(maxLinesOrAll), - textStyle, + widget.code.lines(maxLinesOrAll), + widget.textStyle, ); } } diff --git a/lib/src/presentation/line_numbers_wrapper.dart b/lib/src/presentation/line_numbers_wrapper.dart index 28bd120..c417db6 100644 --- a/lib/src/presentation/line_numbers_wrapper.dart +++ b/lib/src/presentation/line_numbers_wrapper.dart @@ -6,27 +6,31 @@ import 'package:kode_view/src/utils/extensions/text_extensions.dart'; class LineNumbersWrapper extends StatelessWidget { const LineNumbersWrapper({ - required this.enableLineNumbers, - required this.linesNumber, + required this.showLineNumbers, + required this.height, + required this.scrollController, required this.child, + required this.linesNumber, this.fontSize, this.lineNumberBackgroundColor, this.lineNumberColor, super.key, }); - final bool enableLineNumbers; + final bool showLineNumbers; + final double height; final int linesNumber; final Color? lineNumberBackgroundColor; final Color? lineNumberColor; final double? fontSize; final Widget child; + final ScrollController scrollController; @override Widget build(BuildContext context) { double maxWidth = 0; for (int i = 0; i < linesNumber; i++) { String lineNumber = i.toString(); - double width = lineNumber.getTextWidth( + final width = lineNumber.getTextWidth( TextStyles.lineNumber( color: lineNumberColor, fontSize: fontSize, @@ -34,16 +38,19 @@ class LineNumbersWrapper extends StatelessWidget { ); maxWidth = max(maxWidth, width); } - return Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (enableLineNumbers) ...[ + if (showLineNumbers) ...[ Container( constraints: BoxConstraints( maxWidth: maxWidth + 10, + maxHeight: height, ), color: lineNumberBackgroundColor, child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + controller: scrollController, shrinkWrap: true, itemCount: linesNumber, itemBuilder: (context, index) { diff --git a/lib/src/utils/extensions/text_extensions.dart b/lib/src/utils/extensions/text_extensions.dart index f7606eb..3098d6b 100644 --- a/lib/src/utils/extensions/text_extensions.dart +++ b/lib/src/utils/extensions/text_extensions.dart @@ -51,7 +51,10 @@ extension TextExtensions on String { double getTextWidth(TextStyle? style) { final textPainter = TextPainter( - text: TextSpan(text: this, style: style), + text: TextSpan( + text: this, + style: style, + ), textDirection: TextDirection.ltr, )..layout(); return textPainter.size.width; From f707803e357289238f728e4856d379c842517376 Mon Sep 17 00:00:00 2001 From: tkadziolka Date: Fri, 22 Nov 2024 22:18:20 +0100 Subject: [PATCH 7/8] Fixed bouncing scroll --- .flutter-plugins-dependencies | 2 +- example/lib/main.dart | 7 +++++++ example/pubspec.lock | 2 +- lib/src/presentation/code_edit_text.dart | 19 +++++++++++++++---- .../presentation/line_numbers_wrapper.dart | 2 +- lib/src/presentation/styles/text_styles.dart | 1 + 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index ec0b402..3a2dc0d 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"highlights_plugin","path":"/Users/tkadziolka/.pub-cache/hosted/pub.dev/highlights_plugin-0.2.0/","native_build":true,"dependencies":[]}],"android":[{"name":"highlights_plugin","path":"/Users/tkadziolka/.pub-cache/hosted/pub.dev/highlights_plugin-0.2.0/","native_build":true,"dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"highlights_plugin","dependencies":[]}],"date_created":"2024-11-18 21:40:21.617016","version":"3.24.0","swift_package_manager_enabled":false} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"highlights_plugin","path":"/Users/tkadziolka/.pub-cache/hosted/pub.dev/highlights_plugin-0.2.0/","native_build":true,"dependencies":[]}],"android":[{"name":"highlights_plugin","path":"/Users/tkadziolka/.pub-cache/hosted/pub.dev/highlights_plugin-0.2.0/","native_build":true,"dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"highlights_plugin","dependencies":[]}],"date_created":"2024-11-22 20:35:46.447909","version":"3.24.0","swift_package_manager_enabled":false} \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 7f206e8..7a2c216 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -58,6 +58,13 @@ class _MyAppState extends State { controller: controller, showCursor: true, showLineNumbers: true, + maxLines: 5, + decoration: const InputDecoration( + contentPadding: EdgeInsets.zero, + border: OutlineInputBorder( + gapPadding: 0, + ), + ), ), ), ], diff --git a/example/pubspec.lock b/example/pubspec.lock index fb15d61..e168564 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -110,7 +110,7 @@ packages: path: ".." relative: true source: path - version: "0.1.0" + version: "0.2.0" leak_tracker: dependency: transitive description: diff --git a/lib/src/presentation/code_edit_text.dart b/lib/src/presentation/code_edit_text.dart index b84d8ce..acc0876 100644 --- a/lib/src/presentation/code_edit_text.dart +++ b/lib/src/presentation/code_edit_text.dart @@ -66,7 +66,10 @@ class _CodeEditTextState extends State { code: _controller.text, language: widget.language, theme: widget.theme, - textStyle: widget.textStyle, + textStyle: widget.textStyle.copyWith( + height: 1.5, + fontSize: 10.0, + ), ); }); @@ -74,7 +77,10 @@ class _CodeEditTextState extends State { code: _controller.text, language: widget.language, theme: widget.theme, - textStyle: widget.textStyle, + textStyle: widget.textStyle.copyWith( + height: 1.5, + fontSize: 10.0, + ), ); _textFieldScrollController.addListener(() { @@ -106,15 +112,20 @@ class _CodeEditTextState extends State { showLineNumbers: widget.showLineNumbers, scrollController: _lineNumbersScrollController, linesNumber: _controller.text.split('\n').length, - fontSize: widget.textStyle.fontSize, + fontSize: 10.0, child: SingleChildScrollView( scrollDirection: Axis.horizontal, + physics: const ClampingScrollPhysics(), child: IntrinsicWidth( child: TextField( + scrollPadding: EdgeInsets.zero, key: _textFieldKey, scrollController: _textFieldScrollController, controller: _controller, - style: widget.textStyle, + style: widget.textStyle.copyWith( + height: 1.5, + fontSize: 10.0, + ), onTap: widget.onTap, minLines: 1, maxLines: widget.maxLines, diff --git a/lib/src/presentation/line_numbers_wrapper.dart b/lib/src/presentation/line_numbers_wrapper.dart index c417db6..be7fffe 100644 --- a/lib/src/presentation/line_numbers_wrapper.dart +++ b/lib/src/presentation/line_numbers_wrapper.dart @@ -49,7 +49,7 @@ class LineNumbersWrapper extends StatelessWidget { ), color: lineNumberBackgroundColor, child: ListView.builder( - physics: const NeverScrollableScrollPhysics(), + physics: const ClampingScrollPhysics(), controller: scrollController, shrinkWrap: true, itemCount: linesNumber, diff --git a/lib/src/presentation/styles/text_styles.dart b/lib/src/presentation/styles/text_styles.dart index 2ddd790..ff4abef 100644 --- a/lib/src/presentation/styles/text_styles.dart +++ b/lib/src/presentation/styles/text_styles.dart @@ -11,5 +11,6 @@ class TextStyles extends TextStyle { color: Colors.black54, fontSize: fontSize ?? 16, fontStyle: FontStyle.normal, + height: 1.5, ); } From 2384f890e13711cdd7a1e6672ec13cde047a1666 Mon Sep 17 00:00:00 2001 From: tkadziolka Date: Fri, 22 Nov 2024 23:17:40 +0100 Subject: [PATCH 8/8] Simplified max width --- lib/src/presentation/code_edit_text.dart | 2 +- lib/src/presentation/code_text_view.dart | 2 +- .../presentation/line_numbers_wrapper.dart | 44 ++++++++----------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/lib/src/presentation/code_edit_text.dart b/lib/src/presentation/code_edit_text.dart index acc0876..d63e3e6 100644 --- a/lib/src/presentation/code_edit_text.dart +++ b/lib/src/presentation/code_edit_text.dart @@ -111,7 +111,7 @@ class _CodeEditTextState extends State { height: _textFieldHeight, showLineNumbers: widget.showLineNumbers, scrollController: _lineNumbersScrollController, - linesNumber: _controller.text.split('\n').length, + linesCount: _controller.text.split('\n').length, fontSize: 10.0, child: SingleChildScrollView( scrollDirection: Axis.horizontal, diff --git a/lib/src/presentation/code_text_view.dart b/lib/src/presentation/code_text_view.dart index 2740b43..e77c72c 100644 --- a/lib/src/presentation/code_text_view.dart +++ b/lib/src/presentation/code_text_view.dart @@ -81,7 +81,7 @@ class _CodeTextViewState extends State { scrollController: controller, showLineNumbers: widget.enableLineNumbers, height: height, - linesNumber: maxLinesOrAll, + linesCount: maxLinesOrAll, fontSize: widget.textStyle.fontSize, child: SelectableText.rich( TextSpan(children: value.requireData), diff --git a/lib/src/presentation/line_numbers_wrapper.dart b/lib/src/presentation/line_numbers_wrapper.dart index be7fffe..f2666b4 100644 --- a/lib/src/presentation/line_numbers_wrapper.dart +++ b/lib/src/presentation/line_numbers_wrapper.dart @@ -6,68 +6,60 @@ import 'package:kode_view/src/utils/extensions/text_extensions.dart'; class LineNumbersWrapper extends StatelessWidget { const LineNumbersWrapper({ + required this.scrollController, required this.showLineNumbers, + required this.linesCount, required this.height, - required this.scrollController, required this.child, - required this.linesNumber, this.fontSize, this.lineNumberBackgroundColor, this.lineNumberColor, super.key, }); + + final ScrollController scrollController; final bool showLineNumbers; + final int linesCount; final double height; - final int linesNumber; + final Widget child; final Color? lineNumberBackgroundColor; final Color? lineNumberColor; final double? fontSize; - final Widget child; - final ScrollController scrollController; @override Widget build(BuildContext context) { - double maxWidth = 0; - for (int i = 0; i < linesNumber; i++) { - String lineNumber = i.toString(); - final width = lineNumber.getTextWidth( - TextStyles.lineNumber( - color: lineNumberColor, - fontSize: fontSize, - ), - ); - maxWidth = max(maxWidth, width); - } + final textStyle = TextStyles.lineNumber( + color: lineNumberColor, + fontSize: fontSize, + ); + + final maxLineWidth = (linesCount).toString().getTextWidth(textStyle); + return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (showLineNumbers) ...[ Container( constraints: BoxConstraints( - maxWidth: maxWidth + 10, + maxWidth: maxLineWidth, maxHeight: height, ), color: lineNumberBackgroundColor, child: ListView.builder( + shrinkWrap: true, physics: const ClampingScrollPhysics(), controller: scrollController, - shrinkWrap: true, - itemCount: linesNumber, + itemCount: linesCount, itemBuilder: (context, index) { return Text( '${index + 1}', textAlign: TextAlign.right, - style: TextStyles.lineNumber( - color: lineNumberColor, - fontSize: fontSize, - ), + style: textStyle, ); }, ), ), - const SizedBox( - width: 4, - ), + const SizedBox(width: 4), ], Expanded( child: child,