Skip to content
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
16 changes: 8 additions & 8 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
Expand Down Expand Up @@ -53,10 +53,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.3.3"
ffi:
dependency: transitive
description:
Expand Down Expand Up @@ -116,10 +116,10 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.8"
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
Expand Down Expand Up @@ -345,10 +345,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "14.3.1"
version: "15.0.0"
web:
dependency: transitive
description:
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
Expand All @@ -45,6 +46,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
16 changes: 13 additions & 3 deletions lib/controller/custom_text_controller.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_highlight/themes/atom-one-dark-reasonable.dart';
import 'package:highlight/highlight.dart' show highlight, Node;
import 'package:phone_ide/highlight_engine/highlight_engine.dart';

class TextEditingControllerIDE extends TextEditingController {
TextEditingControllerIDE({Key? key, this.font});
TextEditingControllerIDE({this.font, this.useHighlightJs = true}) : super();

static String language = 'HTML';
final TextStyle? font;
final bool useHighlightJs;

List<TextSpan> _convert(List<Node> nodes) {
List<TextSpan> spans = [];
Expand Down Expand Up @@ -56,7 +58,15 @@ class TextEditingControllerIDE extends TextEditingController {
TextStyle? style,
required bool withComposing,
}) {
var nodes = highlight.parse(text, language: language).nodes!;
return TextSpan(style: style, children: _convert(nodes));
if (!useHighlightJs) {
final highlighted = HighlightEngine().highlight(
text,
language: language.toLowerCase(),
);
return TextSpan(style: style, children: highlighted.children);
} else {
var nodes = highlight.parse(text, language: language).nodes!;
return TextSpan(style: style, children: _convert(nodes));
}
}
}
19 changes: 16 additions & 3 deletions lib/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ class EditorState extends State<Editor> {
ScrollController horizontalController = ScrollController();
ScrollController linebarController = ScrollController();

TextEditingControllerIDE beforeController = TextEditingControllerIDE();
TextEditingControllerIDE inController = TextEditingControllerIDE();
TextEditingControllerIDE afterController = TextEditingControllerIDE();
late TextEditingControllerIDE beforeController;
late TextEditingControllerIDE inController;
late TextEditingControllerIDE afterController;

late StreamSubscription<TextFieldData> _textfieldDataSub;

Expand All @@ -64,6 +64,19 @@ class EditorState extends State<Editor> {
@override
void initState() {
super.initState();

beforeController = TextEditingControllerIDE(
useHighlightJs: widget.options.useHighlightJs,
);

inController = TextEditingControllerIDE(
useHighlightJs: widget.options.useHighlightJs,
);

afterController = TextEditingControllerIDE(
useHighlightJs: widget.options.useHighlightJs,
);

handleFileInit();

_textfieldDataSub = widget.textfieldData.stream.listen((event) {
Expand Down
4 changes: 4 additions & 0 deletions lib/editor/editor_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class EditorOptions {
this.showLinebar = true,
this.takeFullHeight = true,
this.isEditable = true,
this.useHighlightJs = true,
this.fontFamily,
});

Expand Down Expand Up @@ -37,6 +38,9 @@ class EditorOptions {
bool showLinebar;

String? fontFamily;

// [useHighlightJs] is used to determine if the editor should use highlight.js for syntax highlighting.
bool useHighlightJs;
}

class EditorRegionOptions {
Expand Down
169 changes: 169 additions & 0 deletions lib/highlight_engine/highlight_engine.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import 'package:flutter/painting.dart';
import 'package:phone_ide/highlight_engine/highlight_rules.dart';

/// Engine to highlight HTML (default), CSS, or JavaScript source.
class HighlightEngine {
static final TextStyle defaultStyle = HighlightStyles.defaultStyle;
static final TextStyle commentStyle = HighlightStyles.commentStyle;
static final TextStyle stringStyle = HighlightStyles.stringStyle;
static final TextStyle keywordStyle = HighlightStyles.keywordStyle;
static final TextStyle tagStyle = HighlightStyles.tagStyle;
static final TextStyle attrNameStyle = HighlightStyles.attrNameStyle;
static final TextStyle cssPropStyle = HighlightStyles.cssPropStyle;
static final TextStyle numberStyle = HighlightStyles.numberStyle;
static final TextStyle cssSelectorStyle = HighlightStyles.cssSelectorStyle;
static final TextStyle cssPropertyStyle = HighlightStyles.cssPropertyStyle;
static final TextStyle cssValueStyle = HighlightStyles.cssValueStyle;

/// Highlights [sourceCode] according to [language]: 'html', 'css', or 'js'.
TextSpan highlight(String sourceCode, {String language = 'html'}) {
switch (language.toLowerCase()) {
case 'css':
return _highlightCss(sourceCode);
case 'js':
case 'javascript':
return _highlightJs(sourceCode);
case 'html':
default:
return _highlightHtml(sourceCode);
}
}

TextSpan _highlightHtml(String sourceCode) {
final children = <TextSpan>[];
final lines = sourceCode.split('\n');
bool inStyle = false, inScript = false;
int braceCount = 0;

for (var line in lines) {
if (!inStyle && !inScript) {
if (_handleStyleOpen(line, children)) {
inStyle = true;
braceCount = 0;
} else if (_handleScriptOpen(line, children)) {
inScript = true;
} else {
children.addAll(_highlightLine(line, htmlRules));
children.add(TextSpan(text: '\n', style: defaultStyle));
}
} else if (inStyle) {
if (_handleStyleClose(line, children, braceCount)) {
inStyle = false;
braceCount = 0;
} else {
final rules = braceCount == 0 ? cssSelectorRules : cssPropertyRules;
children.addAll(_highlightLine(line, rules));
children.add(TextSpan(text: '\n', style: defaultStyle));
braceCount += RegExp(r'\{').allMatches(line).length;
braceCount -= RegExp(r'\}').allMatches(line).length;
}
} else {
// inScript
if (_handleScriptClose(line, children)) {
inScript = false;
} else {
children.addAll(_highlightLine(line, jsRules));
children.add(TextSpan(text: '\n', style: defaultStyle));
}
}
}
if (children.isNotEmpty && children.last.toPlainText() == '\n') {
children.removeLast();
}
return TextSpan(style: defaultStyle, children: children);
}

TextSpan _highlightCss(String sourceCode) {
final children = <TextSpan>[];
int braceCount = 0;
for (var line in sourceCode.split('\n')) {
final rules = braceCount == 0 ? cssSelectorRules : cssPropertyRules;
children.addAll(_highlightLine(line, rules));
children.add(TextSpan(text: '\n', style: defaultStyle));
braceCount += RegExp(r'\{').allMatches(line).length;
braceCount -= RegExp(r'\}').allMatches(line).length;
}
if (children.isNotEmpty && children.last.toPlainText() == '\n') {
children.removeLast();
}
return TextSpan(style: defaultStyle, children: children);
}

TextSpan _highlightJs(String sourceCode) {
final children = <TextSpan>[];
for (var line in sourceCode.split('\n')) {
children.addAll(_highlightLine(line, jsRules));
children.add(TextSpan(text: '\n', style: defaultStyle));
}
if (children.isNotEmpty && children.last.toPlainText() == '\n') {
children.removeLast();
}
return TextSpan(style: defaultStyle, children: children);
}

bool _handleStyleOpen(String line, List<TextSpan> children) {
final start = RegExp(r'<\s*style\b', caseSensitive: false);
final end = RegExp(r'<\s*/\s*style\s*>', caseSensitive: false);
if (start.hasMatch(line)) {
children.addAll(_highlightLine(line, htmlRules));
return !end.hasMatch(line);
}
return false;
}

bool _handleScriptOpen(String line, List<TextSpan> children) {
final start = RegExp(r'<\s*script\b', caseSensitive: false);
final end = RegExp(r'<\s*/\s*script\s*>', caseSensitive: false);
if (start.hasMatch(line)) {
children.addAll(_highlightLine(line, htmlRules));
return !end.hasMatch(line);
}
return false;
}

bool _handleStyleClose(String line, List<TextSpan> children, int braceCount) {
final end = RegExp(r'<\s*/\s*style\s*>', caseSensitive: false);
if (end.hasMatch(line)) {
children.addAll(_highlightLine(line, htmlRules));
return true;
}
return false;
}

bool _handleScriptClose(String line, List<TextSpan> children) {
final end = RegExp(r'<\s*/\s*script\s*>', caseSensitive: false);
if (end.hasMatch(line)) {
children.addAll(_highlightLine(line, htmlRules));
return true;
}
return false;
}

List<TextSpan> _highlightLine(String line, List<HighlightRule> rules) {
final spans = <TextSpan>[];
var buffer = '';
var i = 0;
while (i < line.length) {
var matched = false;
for (final rule in rules) {
final m = rule.regex.matchAsPrefix(line, i);
if (m != null) {
if (buffer.isNotEmpty) {
spans.add(TextSpan(text: buffer, style: defaultStyle));
buffer = '';
}
final token = m[0]!;
spans.add(TextSpan(text: token, style: rule.style));
i += token.length;
matched = true;
break;
}
}
if (!matched) buffer += line[i++];
}
if (buffer.isNotEmpty) {
spans.add(TextSpan(text: buffer, style: defaultStyle));
}
return spans;
}
}
Loading