Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/line numbers #8

Open
wants to merge 8 commits into
base: release/0.2.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flutter-plugins-dependencies
Original file line number Diff line number Diff line change
@@ -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}
{"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}
9 changes: 9 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class _MyAppState extends State<MyApp> {
selectAll: true,
share: true,
),
enableLineNumbers: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

showLineNumbers

),
),
const SizedBox(height: 24),
Expand All @@ -56,6 +57,14 @@ class _MyAppState extends State<MyApp> {
code: codeSnippet,
controller: controller,
showCursor: true,
showLineNumbers: true,
maxLines: 5,
decoration: const InputDecoration(
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
gapPadding: 0,
),
),
),
),
],
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.1.0"
version: "0.2.0"
leak_tracker:
dependency: transitive
description:
Expand Down
119 changes: 90 additions & 29 deletions lib/src/presentation/code_edit_text.dart
Original file line number Diff line number Diff line change
@@ -1,84 +1,145 @@
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,
this.controller,
this.maxLines,
this.options,
this.showCursor,
this.onTap,
this.language,
this.theme,
this.textStyle,
this.textStyle = const TextStyles.code(),
this.maxLines,
this.decoration,
this.showLineNumbers = false,
this.lineNumberColor,
this.lineNumberBackgroundColor,
this.showCursor,
this.options,
this.onTap,
this.debug = false,
super.key,
});

final String code;

final SyntaxHighlightingController? controller;
final String? language;
final String? theme;
final TextStyle textStyle;
final int? maxLines;

final InputDecoration? decoration;
final bool showLineNumbers;
final Color? lineNumberColor;
final Color? lineNumberBackgroundColor;

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;
final bool debug;

@override
State<CodeEditText> createState() => _CodeEditTextState();
}

class _CodeEditTextState extends State<CodeEditText> {
late SyntaxHighlightingController _controller;
final ScrollController _lineNumbersScrollController = ScrollController();
final ScrollController _textFieldScrollController = ScrollController();
final GlobalKey _textFieldKey = GlobalKey();
double _textFieldHeight = 0;

@override
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,
theme: widget.theme,
textStyle: widget.textStyle,
textStyle: widget.textStyle.copyWith(
height: 1.5,
fontSize: 10.0,
),
);
});

_controller.updateSyntaxHighlighting(
code: _controller.text,
language: widget.language,
theme: widget.theme,
textStyle: widget.textStyle,
textStyle: widget.textStyle.copyWith(
height: 1.5,
fontSize: 10.0,
),
);

_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
Widget build(BuildContext context) {
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,
WidgetsBinding.instance.addPostFrameCallback((_) {
_getTextFieldHeight();
});
return LineNumbersWrapper(
height: _textFieldHeight,
showLineNumbers: widget.showLineNumbers,
scrollController: _lineNumbersScrollController,
linesCount: _controller.text.split('\n').length,
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.copyWith(
height: 1.5,
fontSize: 10.0,
),
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,
),
),
),
);
},
);
Expand Down
81 changes: 58 additions & 23 deletions lib/src/presentation/code_text_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ 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';
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,
Expand All @@ -16,20 +17,23 @@ class CodeTextView extends StatelessWidget {
this.onTap,
this.language,
this.theme,
this.textStyle,
this.textStyle = const TextStyles.code(),
this.enableLineNumbers = false,
super.key,
});

final splitter = const LineSplitter();

final String code;
final int? maxLines;
final bool? showCursor;
final TextSelectionOptions? options;
final GestureTapCallback? onTap;
final bool enableLineNumbers;

final String? language;
final String? theme;
final TextStyle? textStyle;
final TextStyle textStyle;

const CodeTextView.preview({
required this.code,
Expand All @@ -38,46 +42,77 @@ class CodeTextView extends StatelessWidget {
this.showCursor,
this.language,
this.theme,
this.textStyle,
this.textStyle = const TextStyles.code(),
this.enableLineNumbers = false,
super.key,
}) : maxLines = 5;

@override
State<CodeTextView> createState() => _CodeTextViewState();
}

class _CodeTextViewState extends State<CodeTextView> {
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 <TextSpan>[],
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(
scrollController: controller,
showLineNumbers: widget.enableLineNumbers,
height: height,
linesCount: maxLinesOrAll,
fontSize: widget.textStyle.fontSize,
child: SelectableText.rich(
TextSpan(children: value.requireData),
key: selectableTextkey,
style: widget.textStyle,
minLines: 1,
maxLines: maxLinesOrAll,
onTap: () {},
contextMenuBuilder: widget.options != null
? (context, editableTextState) =>
widget.options!.toolbarOptions(context, editableTextState)
: null,
enableInteractiveSelection: widget.options != null,
showCursor: widget.showCursor ?? false,
scrollPhysics: const ClampingScrollPhysics(),
),
);
},
);
}

Future<List<TextSpan>> _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 ?? TextStyles.code(code).style!,
widget.code.lines(maxLinesOrAll),
widget.textStyle,
);
}
}
Loading