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

flutter hightlight added text editing #10

Open
wants to merge 2 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
10 changes: 8 additions & 2 deletions flutter_highlight/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@ PODS:
- Flutter (1.0.0)
- url_launcher (0.0.1):
- Flutter
- url_launcher_macos (0.0.1):
- Flutter
- url_launcher_web (0.0.1):
- Flutter

DEPENDENCIES:
- Flutter (from `Flutter`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`)
- url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`)

EXTERNAL SOURCES:
Flutter:
:path: Flutter
url_launcher:
:path: ".symlinks/plugins/url_launcher/ios"
url_launcher_macos:
:path: ".symlinks/plugins/url_launcher_macos/ios"
url_launcher_web:
:path: ".symlinks/plugins/url_launcher_web/ios"

SPEC CHECKSUMS:
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
url_launcher: a1c0cc845906122c4784c542523d8cacbded5626
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313
url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c

PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83

COCOAPODS: 1.8.4
COCOAPODS: 1.9.1
47 changes: 32 additions & 15 deletions flutter_highlight/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(title),
actions: <Widget>[
Expand Down Expand Up @@ -91,21 +92,37 @@ class _MyHomePageState extends State<MyHomePage> {
)
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
HighlightView(
exampleMap[language],
language: language,
theme: themeMap[theme],
padding: EdgeInsets.all(12),
textStyle: TextStyle(
fontFamily:
'SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace'),
)
],
),
body: LayoutBuilder(
builder: (context, constraints) {
final highlightView = HighlightView(
exampleMap[language],
readOnly: false,
language: language,
theme: themeMap[theme],
padding: EdgeInsets.all(12),
textStyle: TextStyle(
fontFamily:
'SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace',
),
);

if (constraints.maxWidth > 600) {
return Center(
child: Container(
constraints: BoxConstraints(
maxWidth: 600,
maxHeight: 800,
),
child: highlightView,
),
);
} else {
return SizedBox(
height: MediaQuery.of(context).size.height,
child: highlightView,
);
}
},
),
);
}
Expand Down
152 changes: 134 additions & 18 deletions flutter_highlight/lib/flutter_highlight.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:highlight/highlight.dart' show highlight, Node;
import 'package:linked_scroll_controller/linked_scroll_controller.dart';

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

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

/// Code edition controller
///
/// Required if property [readOnly] changed to false
final TextEditingController controller;

/// Code edition availability
final bool readOnly;

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

static const _rootKey = 'root';
static const _defaultFontColor = Color(0xff000000);
static const _defaultBackgroundColor = Color(0xffffffff);

// TODO: dart:io is not available at web platform currently
// See: https://github.com/flutter/flutter/issues/39998
// So we just use monospace here for now
static const _defaultFontFamily = 'monospace';

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

class _HighlightViewState extends State<HighlightView> {
LinkedScrollControllerGroup _controllers;
ScrollController _highlightScrollController, _editableScrollView;
TextEditingController _textController;
String _editableText;

@override
void initState() {
_controllers = LinkedScrollControllerGroup();
_highlightScrollController = _controllers.addAndGet();
_editableScrollView = _controllers.addAndGet();
_textController = widget.controller ?? TextEditingController();
_textController.text = widget.source;
_editableText = widget.source;
super.initState();
}

@override
void dispose() {
_highlightScrollController.dispose();
_editableScrollView.dispose();
if (widget.controller == null) {
_textController.dispose();
}
super.dispose();
}

@override
void didUpdateWidget(HighlightView oldWidget) {
if (oldWidget.source != widget.source) {
_textController.value = TextEditingValue(text: widget.source);
_editableText = widget.source;
}
super.didUpdateWidget(oldWidget);
}

List<TextSpan> _convert(List<Node> nodes) {
List<TextSpan> spans = [];
var currentSpans = spans;
Expand All @@ -45,10 +107,11 @@ class HighlightView extends StatelessWidget {
if (node.value != null) {
currentSpans.add(node.className == null
? TextSpan(text: node.value)
: TextSpan(text: node.value, style: theme[node.className]));
: TextSpan(text: node.value, style: widget.theme[node.className]));
} else if (node.children != null) {
List<TextSpan> tmp = [];
currentSpans.add(TextSpan(children: tmp, style: theme[node.className]));
currentSpans
.add(TextSpan(children: tmp, style: widget.theme[node.className]));
stack.add(currentSpans);
currentSpans = tmp;

Expand All @@ -68,32 +131,85 @@ class HighlightView extends StatelessWidget {
return spans;
}

static const _rootKey = 'root';
static const _defaultFontColor = Color(0xff000000);
static const _defaultBackgroundColor = Color(0xffffffff);

// TODO: dart:io is not available at web platform currently
// See: https://github.com/flutter/flutter/issues/39998
// So we just use monospace here for now
static const _defaultFontFamily = 'monospace';
int get _linesCount => '\n'.allMatches(_editableText).length;

@override
Widget build(BuildContext context) {
final media = MediaQuery.of(context);
var padding = widget.padding ?? EdgeInsets.zero;
// Keyboard height
if (!widget.readOnly) {
padding = padding.add(media.viewInsets);
}

var _textStyle = TextStyle(
fontFamily: _defaultFontFamily,
color: theme[_rootKey]?.color ?? _defaultFontColor,
letterSpacing: 0,
fontSize: 16,
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,
Widget content = Padding(
padding: padding,
child: RichText(
text: TextSpan(
style: _textStyle,
children: _convert(highlight.parse(source, language: language).nodes),
children: _convert(
highlight.parse(_editableText, language: widget.language).nodes,
),
),
),
);

if (!widget.readOnly) {
content = SingleChildScrollView(
controller: _highlightScrollController,
child: content,
);
}

return NotificationListener(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
if (notification.scrollDelta.abs() > 10 &&
notification.dragDetails != null)
SystemChannels.textInput.invokeMethod('TextInput.hide');
}
return true;
},
child: Container(
color: widget.theme[HighlightView._rootKey]?.backgroundColor ??
HighlightView._defaultBackgroundColor,
child: Stack(
children: <Widget>[
content,
if (!widget.readOnly)
CupertinoTextField(
scrollController: _editableScrollView,
scrollPadding: EdgeInsets.zero,
maxLines: _linesCount,
maxLengthEnforced: false,
keyboardType: TextInputType.multiline,
controller: _textController,
padding: padding,
style: _textStyle.copyWith(
color: Colors.transparent,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
color: Colors.transparent,
),
onChanged: (value) {
setState(() {
_editableText = value;
});
},
),
],
),
),
);
Expand Down
1 change: 1 addition & 0 deletions flutter_highlight/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies:
flutter:
sdk: flutter
highlight: ^0.6.0
linked_scroll_controller: ^0.1.2

dev_dependencies:
flutter_test:
Expand Down