A lib/ansi.dart => lib/ansi.dart +148 -0
@@ 0,0 1,148 @@
+import 'package:flutter/rendering.dart';
+
+/// Strip ANSI formatting as defined in:
+/// https://modern.ircdocs.horse/formatting.html
+String stripAnsiFormatting(String s) {
+ var out = '';
+ for (var i = 0; i < s.length; i++) {
+ var ch = s[i];
+ switch (ch) {
+ case '\x02': // bold
+ case '\x1D': // italic
+ case '\x1F': // underline
+ case '\x1E': // strike-through
+ case '\x11': // monospace
+ case '\x16': // reverse color
+ case '\x0F': // reset
+ break; // skip
+ case '\x03': // color
+ if (i + 1 >= s.length || !_isDigit(s[i + 1])) {
+ break;
+ }
+ i++;
+ if (i + 1 < s.length && _isDigit(s[i + 1])) {
+ i++;
+ }
+ if (i + 2 < s.length && s[i + 1] == ',' && _isDigit(s[i + 2])) {
+ i += 2;
+ if (i + 1 < s.length && _isDigit(s[i + 1])) {
+ i++;
+ }
+ }
+ break;
+ case '\x04': // hex color
+ i += 6;
+ break;
+ default:
+ out += ch;
+ }
+ }
+ return out;
+}
+
+var colorHexCodes = [
+ 0xffffffff, 0xff000000, 0xff00007f, 0xff009300, 0xffff0000, 0xff7f0000, 0xff9c009c, 0xfffc7f00, 0xffffff00, 0xff00fc00, 0xff009393, 0xff00ffff, 0xff0000fc, 0xffff00ff, 0xff7f7f7f, 0xff2d2d2d,
+ 0xff470000, 0xff472100, 0xff474700, 0xff324700, 0xff004700, 0xff00472c, 0xff004747, 0xff002747, 0xff000047, 0xff2e0047, 0xff470047, 0xff47002a,
+ 0xff740000, 0xff743a00, 0xff747400, 0xff517400, 0xff007400, 0xff007449, 0xff007474, 0xff004074, 0xff000074, 0xff4b0074, 0xff740074, 0xff740045,
+ 0xffb50000, 0xffb56300, 0xffb5b500, 0xff7db500, 0xff00b500, 0xff00b571, 0xff00b5b5, 0xff0063b5, 0xff0000b5, 0xff7500b5, 0xffb500b5, 0xffb5006b,
+ 0xffff0000, 0xffff8c00, 0xffffff00, 0xffb2ff00, 0xff00ff00, 0xff00ffa0, 0xff00ffff, 0xff008cff, 0xff0000ff, 0xffa500ff, 0xffff00ff, 0xffff0098,
+ 0xffff5959, 0xffffb459, 0xffffff71, 0xffcfff60, 0xff6fff6f, 0xff65ffc9, 0xff6dffff, 0xff59b4ff, 0xff5959ff, 0xffc459ff, 0xffff66ff, 0xffff59bc,
+ 0xffff9c9c, 0xffffd39c, 0xffffff9c, 0xffe2ff9c, 0xff9cff9c, 0xff9cffdb, 0xff9cffff, 0xff9cd3ff, 0xff9c9cff, 0xffdc9cff, 0xffff9cff, 0xffff94d3,
+ 0xff000000, 0xff131313, 0xff282828, 0xff363636, 0xff4d4d4d, 0xff656565, 0xff818181, 0xff9f9f9f, 0xffbcbcbc, 0xffe2e2e2, 0xffffffff,
+];
+
+/// Apply ANSI formatting as defined in:
+/// https://modern.ircdocs.horse/formatting.html
+List<TextSpan> applyAnsiFormatting(String s, TextStyle base) {
+ var current = StringBuffer();
+ List<TextSpan> spans = [];
+ var bold = false;
+ var italic = false;
+ var underline = false;
+ Color? fgColor;
+ Color? bgColor;
+ for (var i = 0; i <= s.length; i++) {
+ var ch = i == s.length ? '\x0F' : s[i];
+ switch (ch) {
+ case '\x0F': // reset
+ case '\x02': // bold
+ case '\x1D': // italic
+ case '\x1F': // underline
+ case '\x03': // color
+ List<TextDecoration> decorations = [base.decoration ?? TextDecoration.none];
+ if (underline) {
+ decorations.add(TextDecoration.underline);
+ }
+ spans.add(TextSpan(text: current.toString(), style: base.copyWith(
+ fontWeight: bold ? FontWeight.bold : null,
+ fontStyle: italic ? FontStyle.italic : null,
+ decoration: TextDecoration.combine(decorations),
+ color: fgColor,
+ backgroundColor: bgColor,
+ )));
+ current.clear();
+ }
+ if (i == s.length) {
+ break;
+ }
+ switch (ch) {
+ case '\x0F': // reset
+ bold = false;
+ italic = false;
+ underline = false;
+ fgColor = null;
+ bgColor = null;
+ break;
+ case '\x02': // bold
+ bold = !bold;
+ break;
+ case '\x1D': // italic
+ italic = !italic;
+ break;
+ case '\x1F': // underline
+ underline = !underline;
+ break;
+ case '\x03': // color
+ if (i + 1 >= s.length || !_isDigit(s[i + 1])) {
+ fgColor = null;
+ bgColor = null;
+ break;
+ }
+ i++;
+ var fg = s[i].codeUnits[0] - '0'.codeUnits[0];
+ if (i + 1 < s.length && _isDigit(s[i + 1])) {
+ i++;
+ fg *= 10;
+ fg += s[i].codeUnits[0] - '0'.codeUnits[0];
+ }
+ fgColor = fg == 99 ? null : Color(colorHexCodes[fg]);
+ if (i + 2 < s.length && s[i + 1] == ',' && _isDigit(s[i + 2])) {
+ i += 2;
+ var bg = s[i].codeUnits[0] - '0'.codeUnits[0];
+ if (i + 1 < s.length && _isDigit(s[i + 1])) {
+ i++;
+ bg *= 10;
+ bg += s[i].codeUnits[0] - '0'.codeUnits[0];
+ }
+ bgColor = bg == 99 ? null : Color(colorHexCodes[bg]);
+ }
+ break;
+ case '\x11': // monospace
+ case '\x1E': // strike-through
+ case '\x16': // reverse color
+ // ignore, rarely used
+ break;
+ case '\x04': // hex color
+ i += 6;
+ // ignore, rarely used
+ break;
+ default:
+ current.write(ch);
+ }
+ }
+ return spans;
+}
+
+bool _isDigit(String ch) {
+ return '0'.codeUnits.first <= ch.codeUnits.first && ch.codeUnits.first <= '9'.codeUnits.first;
+}
M lib/irc.dart => lib/irc.dart +0 -149
@@ 1,8 1,6 @@
import 'dart:collection';
import 'dart:core';
-import 'package:flutter/rendering.dart';
-
// RFC 1459
const RPL_WELCOME = '001';
const RPL_YOURHOST = '002';
@@ 748,153 746,6 @@ class CtcpMessage {
}
}
-/// Strip ANSI formatting as defined in:
-/// https://modern.ircdocs.horse/formatting.html
-String stripAnsiFormatting(String s) {
- var out = '';
- for (var i = 0; i < s.length; i++) {
- var ch = s[i];
- switch (ch) {
- case '\x02': // bold
- case '\x1D': // italic
- case '\x1F': // underline
- case '\x1E': // strike-through
- case '\x11': // monospace
- case '\x16': // reverse color
- case '\x0F': // reset
- break; // skip
- case '\x03': // color
- if (i + 1 >= s.length || !_isDigit(s[i + 1])) {
- break;
- }
- i++;
- if (i + 1 < s.length && _isDigit(s[i + 1])) {
- i++;
- }
- if (i + 2 < s.length && s[i + 1] == ',' && _isDigit(s[i + 2])) {
- i += 2;
- if (i + 1 < s.length && _isDigit(s[i + 1])) {
- i++;
- }
- }
- break;
- case '\x04': // hex color
- i += 6;
- break;
- default:
- out += ch;
- }
- }
- return out;
-}
-
-var colorHexCodes = [
- 0xffffffff, 0xff000000, 0xff00007f, 0xff009300, 0xffff0000, 0xff7f0000, 0xff9c009c, 0xfffc7f00, 0xffffff00, 0xff00fc00, 0xff009393, 0xff00ffff, 0xff0000fc, 0xffff00ff, 0xff7f7f7f, 0xff2d2d2d,
- 0xff470000, 0xff472100, 0xff474700, 0xff324700, 0xff004700, 0xff00472c, 0xff004747, 0xff002747, 0xff000047, 0xff2e0047, 0xff470047, 0xff47002a,
- 0xff740000, 0xff743a00, 0xff747400, 0xff517400, 0xff007400, 0xff007449, 0xff007474, 0xff004074, 0xff000074, 0xff4b0074, 0xff740074, 0xff740045,
- 0xffb50000, 0xffb56300, 0xffb5b500, 0xff7db500, 0xff00b500, 0xff00b571, 0xff00b5b5, 0xff0063b5, 0xff0000b5, 0xff7500b5, 0xffb500b5, 0xffb5006b,
- 0xffff0000, 0xffff8c00, 0xffffff00, 0xffb2ff00, 0xff00ff00, 0xff00ffa0, 0xff00ffff, 0xff008cff, 0xff0000ff, 0xffa500ff, 0xffff00ff, 0xffff0098,
- 0xffff5959, 0xffffb459, 0xffffff71, 0xffcfff60, 0xff6fff6f, 0xff65ffc9, 0xff6dffff, 0xff59b4ff, 0xff5959ff, 0xffc459ff, 0xffff66ff, 0xffff59bc,
- 0xffff9c9c, 0xffffd39c, 0xffffff9c, 0xffe2ff9c, 0xff9cff9c, 0xff9cffdb, 0xff9cffff, 0xff9cd3ff, 0xff9c9cff, 0xffdc9cff, 0xffff9cff, 0xffff94d3,
- 0xff000000, 0xff131313, 0xff282828, 0xff363636, 0xff4d4d4d, 0xff656565, 0xff818181, 0xff9f9f9f, 0xffbcbcbc, 0xffe2e2e2, 0xffffffff,
-];
-
-/// Apply ANSI formatting as defined in:
-/// https://modern.ircdocs.horse/formatting.html
-List<TextSpan> applyAnsiFormatting(String s, TextStyle base) {
- var current = StringBuffer();
- List<TextSpan> spans = [];
- var bold = false;
- var italic = false;
- var underline = false;
- Color? fgColor;
- Color? bgColor;
- for (var i = 0; i <= s.length; i++) {
- var ch = i == s.length ? '\x0F' : s[i];
- switch (ch) {
- case '\x0F': // reset
- case '\x02': // bold
- case '\x1D': // italic
- case '\x1F': // underline
- case '\x03': // color
- List<TextDecoration> decorations = [base.decoration ?? TextDecoration.none];
- if (underline) {
- decorations.add(TextDecoration.underline);
- }
- spans.add(TextSpan(text: current.toString(), style: base.copyWith(
- fontWeight: bold ? FontWeight.bold : null,
- fontStyle: italic ? FontStyle.italic : null,
- decoration: TextDecoration.combine(decorations),
- color: fgColor,
- backgroundColor: bgColor,
- )));
- current.clear();
- }
- if (i == s.length) {
- break;
- }
- switch (ch) {
- case '\x0F': // reset
- bold = false;
- italic = false;
- underline = false;
- fgColor = null;
- bgColor = null;
- break;
- case '\x02': // bold
- bold = !bold;
- break;
- case '\x1D': // italic
- italic = !italic;
- break;
- case '\x1F': // underline
- underline = !underline;
- break;
- case '\x03': // color
- if (i + 1 >= s.length || !_isDigit(s[i + 1])) {
- fgColor = null;
- bgColor = null;
- break;
- }
- i++;
- var fg = s[i].codeUnits[0] - '0'.codeUnits[0];
- if (i + 1 < s.length && _isDigit(s[i + 1])) {
- i++;
- fg *= 10;
- fg += s[i].codeUnits[0] - '0'.codeUnits[0];
- }
- fgColor = fg == 99 ? null : Color(colorHexCodes[fg]);
- if (i + 2 < s.length && s[i + 1] == ',' && _isDigit(s[i + 2])) {
- i += 2;
- var bg = s[i].codeUnits[0] - '0'.codeUnits[0];
- if (i + 1 < s.length && _isDigit(s[i + 1])) {
- i++;
- bg *= 10;
- bg += s[i].codeUnits[0] - '0'.codeUnits[0];
- }
- bgColor = bg == 99 ? null : Color(colorHexCodes[bg]);
- }
- break;
- case '\x11': // monospace
- case '\x1E': // strike-through
- case '\x16': // reverse color
- // ignore, rarely used
- break;
- case '\x04': // hex color
- i += 6;
- // ignore, rarely used
- break;
- default:
- current.write(ch);
- }
- }
- return spans;
-}
-
-bool _isDigit(String ch) {
- return '0'.codeUnits.first <= ch.codeUnits.first && ch.codeUnits.first <= '9'.codeUnits.first;
-}
-
final _alphaNumRegExp = RegExp(r'^[\p{L}0-9]$', unicode: true);
bool _isWordBoundary(String ch) {
M lib/notification_controller.dart => lib/notification_controller.dart +1 -0
@@ 2,6 2,7 @@ import 'dart:async';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'ansi.dart';
import 'database.dart';
import 'irc.dart';
import 'models.dart';
M lib/page/buffer.dart => lib/page/buffer.dart +1 -0
@@ 5,6 5,7 @@ import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
+import '../ansi.dart';
import '../client.dart';
import '../client_controller.dart';
import '../database.dart';
M lib/page/buffer_details.dart => lib/page/buffer_details.dart +1 -0
@@ 1,6 1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
+import '../ansi.dart';
import '../client.dart';
import '../dialog/edit_topic.dart';
import '../irc.dart';
M lib/page/buffer_list.dart => lib/page/buffer_list.dart +1 -1
@@ 1,9 1,9 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
+import '../ansi.dart';
import '../client_controller.dart';
import '../database.dart';
-import '../irc.dart';
import '../models.dart';
import '../page/join.dart';
import '../page/settings.dart';
M lib/page/join.dart => lib/page/join.dart +1 -0
@@ 4,6 4,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'buffer.dart';
+import '../ansi.dart';
import '../irc.dart';
import '../client.dart';
import '../client_controller.dart';
M lib/page/network_details.dart => lib/page/network_details.dart +1 -0
@@ 1,6 1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
+import '../ansi.dart';
import '../client.dart';
import '../irc.dart';
import '../linkify.dart';