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

Editable HighlightView Implementation #16

Open
wants to merge 9 commits into
base: master
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: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
> NOTE: this fork has been archived in favour of hosting my own [flutter_highlight](https://github.com/tusharsadhwani/flutter_highlight) repo for ease of use.

# highlight

Syntax highlighting for Dart and Flutter, which supports lots of languages and themes.
Expand Down
1 change: 1 addition & 0 deletions flutter_highlight/example/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
org.gradle.jvmargs=-Xmx1536M

android.enableR8=true
13 changes: 8 additions & 5 deletions flutter_highlight/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,20 @@ class _MyHomePageState extends State<MyHomePage> {
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
child: ListView(
primary: false,
shrinkWrap: true,
children: <Widget>[
HighlightView(
exampleMap[language],
text: exampleMap[language],
editable: true,
language: language,
theme: themeMap[theme],
padding: EdgeInsets.all(12),
textStyle: TextStyle(
fontFamily:
'SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace'),
fontFamily:
'SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace',
),
)
],
),
Expand Down
2 changes: 2 additions & 0 deletions flutter_highlight/example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: flutter_highlight_gallery
description: A new Flutter project.

publish_to: none

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
Expand Down
210 changes: 161 additions & 49 deletions flutter_highlight/lib/flutter_highlight.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import 'package:flutter/widgets.dart';
import 'package:highlight/highlight.dart' show highlight, Node;

/// Highlight Flutter Widget
class HighlightView extends StatelessWidget {
class HighlightView extends StatefulWidget {
/// The original code to be highlighted
final String source;
final String text;

/// The controller for the highlighted text
final HighlightEditingController controller;

/// Highlight language
///
Expand All @@ -19,6 +22,12 @@ class HighlightView extends StatelessWidget {
/// [All available themes](https://github.com/pd4d10/highlight/blob/master/flutter_highlight/lib/themes)
final Map<String, TextStyle> theme;

/// Editable
///
/// If set to true, this will render a TextField instead.
/// Defaults to false.
final bool editable;

/// Padding
final EdgeInsetsGeometry padding;

Expand All @@ -27,46 +36,17 @@ class HighlightView extends StatelessWidget {
/// Specify text styles such as font family and font size
final TextStyle textStyle;

HighlightView(
String input, {
HighlightView({
this.text,
this.controller,
this.language,
this.theme = const {},
this.editable = false,
this.padding,
this.textStyle,
int tabSize = 8, // TODO: https://github.com/flutter/flutter/issues/50087
}) : source = input.replaceAll('\t', ' ' * tabSize);

List<TextSpan> _convert(List<Node> nodes) {
List<TextSpan> spans = [];
var currentSpans = spans;
List<List<TextSpan>> stack = [];

_traverse(Node node) {
if (node.value != null) {
currentSpans.add(node.className == null
? TextSpan(text: node.value)
: TextSpan(text: node.value, style: theme[node.className]));
} else if (node.children != null) {
List<TextSpan> tmp = [];
currentSpans.add(TextSpan(children: tmp, style: theme[node.className]));
stack.add(currentSpans);
currentSpans = tmp;

node.children.forEach((n) {
_traverse(n);
if (n == node.children.last) {
currentSpans = stack.isEmpty ? spans : stack.removeLast();
}
});
}
}

for (var node in nodes) {
_traverse(node);
}

return spans;
}
}) : assert(text != null || controller != null,
'One of text or controller properties must be provided');

static const _rootKey = 'root';
static const _defaultFontColor = Color(0xff000000);
Expand All @@ -77,25 +57,157 @@ class HighlightView extends StatelessWidget {
// So we just use monospace here for now
static const _defaultFontFamily = 'monospace';

@override
_HighlightViewState createState() => _HighlightViewState();
}

class _HighlightViewState extends State<HighlightView> {
String source;
HighlightEditingController controller;

@override
void initState() {
super.initState();
if (widget.controller == null) {
controller = HighlightEditingController(widget.language, widget.theme);
controller.text = widget.text;
} else {
controller = widget.controller;
}
source = controller.text;
}

@override
Widget build(BuildContext context) {
var _textStyle = TextStyle(
fontFamily: _defaultFontFamily,
color: theme[_rootKey]?.color ?? _defaultFontColor,
fontFamily: HighlightView._defaultFontFamily,
color: widget.theme[HighlightView._rootKey]?.color ??
HighlightView._defaultFontColor,
);
if (textStyle != null) {
_textStyle = _textStyle.merge(textStyle);
if (widget.textStyle != null) {
_textStyle = _textStyle.merge(widget.textStyle);
}

return Container(
color: theme[_rootKey]?.backgroundColor ?? _defaultBackgroundColor,
padding: padding,
child: RichText(
text: TextSpan(
style: _textStyle,
children: _convert(highlight.parse(source, language: language).nodes),
),
),
color: widget.theme[HighlightView._rootKey]?.backgroundColor ??
HighlightView._defaultBackgroundColor,
padding: widget.padding,
child: widget.editable
? TextField(
controller: controller,
decoration: InputDecoration(border: InputBorder.none),
maxLines: null,
style: widget.theme[HighlightView._rootKey],
)
: RichText(
text: TextSpan(
style: _textStyle,
children: getHighlightTextSpan(
source,
widget.language,
widget.theme,
),
),
),
);
}
}

List<TextSpan> getHighlightTextSpan(source, language, theme) {
return _convert(highlight.parse(source, language: language).nodes, theme);
}

class HighlightEditingController extends TextEditingController {
String language;
Map<String, TextStyle> theme;

HighlightEditingController(this.language, this.theme);

@override
TextSpan buildTextSpan({TextStyle style, bool withComposing}) {
final result = highlight.parse(value.text, language: language);
final spans = TextSpan(
style: style,
children: _convert(result.nodes, theme),
);
if (value.composing.isValid && withComposing) {
underlineComposing(spans);
}
return spans;
}

underlineComposing(TextSpan nodes) {
var pos = 0;
final TextStyle composingStyle =
TextStyle(decoration: TextDecoration.underline);

TextSpan _traverse(TextSpan node) {
if (node.text != null &&
pos <= value.composing.start &&
value.composing.end <= pos + node.text.length) {
var relativeComposing = TextRange(
start: value.composing.start - pos,
end: value.composing.end - pos,
);
return TextSpan(
children: [
TextSpan(text: relativeComposing.textBefore(node.text)),
TextSpan(
style: composingStyle,
text: relativeComposing.textInside(node.text),
),
TextSpan(text: relativeComposing.textAfter(node.text)),
],
);
}

pos += node.text?.length ?? 0;
if (node.children != null) {
for (var i = 0;
i < node.children.length && pos <= value.composing.start;
i++) {
var update = _traverse(node.children[i]);
if (update != null) {
node.children[i] = update;
return null;
}
}
}
return null;
}

_traverse(nodes);
}
}

List<TextSpan> _convert(List<Node> nodes, theme) {
List<TextSpan> spans = [];
var currentSpans = spans;
List<List<TextSpan>> stack = [];

_traverse(Node node) {
if (node.value != null) {
currentSpans.add(node.className == null
? TextSpan(text: node.value)
: TextSpan(text: node.value, style: theme[node.className]));
} else if (node.children != null) {
List<TextSpan> tmp = [];
currentSpans.add(TextSpan(children: tmp, style: theme[node.className]));
stack.add(currentSpans);
currentSpans = tmp;

node.children.forEach((n) {
_traverse(n);
if (n == node.children.last) {
currentSpans = stack.isEmpty ? spans : stack.removeLast();
}
});
}
}

for (var node in nodes) {
_traverse(node);
}

return spans;
}
5 changes: 4 additions & 1 deletion flutter_highlight/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ version: 0.6.0+1
author: Rongjian Zhang <[email protected]>
homepage: https://github.com/git-touch/highlight

publish_to: none

environment:
sdk: ">=2.3.0 <3.0.0"

dependencies:
flutter:
sdk: flutter
highlight: ^0.6.0
highlight:
path: ../highlight

dev_dependencies:
flutter_test:
Expand Down