+167
-167
File diff suppressed because it is too large
Load Diff
@@ -11,171 +11,201 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class ClideTheme {
|
||||
// ─── palette ──────────────────────────────────────────────────────────
|
||||
static const _bg = Color(0xFF20202C); // page / editor canvas
|
||||
static const _bgSunken = Color(0xFF1A1A24); // sidebar, gutters
|
||||
static const _surface = Color(0xFF242838); // cards, table header, pills
|
||||
static const _surfaceHi = Color(0xFF2C3046); // hover, selected row
|
||||
static const _border = Color(0xFF343850);
|
||||
static const _borderHi = Color(0xFF3C445C);
|
||||
static const _bg = Color(0xFF20202C); // page / editor canvas
|
||||
static const _bgSunken = Color(0xFF1A1A24); // sidebar, gutters
|
||||
static const _surface = Color(0xFF242838); // cards, table header, pills
|
||||
static const _surfaceHi = Color(0xFF2C3046); // hover, selected row
|
||||
static const _border = Color(0xFF343850);
|
||||
static const _borderHi = Color(0xFF3C445C);
|
||||
|
||||
static const _textHi = Color(0xFFE6E8F2); // primary text
|
||||
static const _text = Color(0xFFB1BBE3); // section titles, labels
|
||||
static const _textDim = Color(0xFF78809C); // secondary
|
||||
static const _textMute = Color(0xFF545C84); // small-caps labels
|
||||
static const _textHi = Color(0xFFE6E8F2); // primary text
|
||||
static const _text = Color(0xFFB1BBE3); // section titles, labels
|
||||
static const _textDim = Color(0xFF78809C); // secondary
|
||||
static const _textMute = Color(0xFF545C84); // small-caps labels
|
||||
|
||||
static const _accent = Color(0xFF78A0F8); // periwinkle primary
|
||||
static const _accent = Color(0xFF78A0F8); // periwinkle primary
|
||||
static const _accentPress = Color(0xFF6C90DC);
|
||||
static const _accentSoft = Color(0x2278A0F8); // 13% accent for fills
|
||||
static const _accentSoft = Color(0x2278A0F8); // 13% accent for fills
|
||||
|
||||
static const _ok = Color(0xFF7DD3A8);
|
||||
static const _warn = Color(0xFFE6C370);
|
||||
static const _err = Color(0xFFE87D7D);
|
||||
static const _info = _accent;
|
||||
static const _ok = Color(0xFF7DD3A8);
|
||||
static const _warn = Color(0xFFE6C370);
|
||||
static const _err = Color(0xFFE87D7D);
|
||||
static const _info = _accent;
|
||||
|
||||
// syntax (Dart-biased)
|
||||
static const _synKeyword = Color(0xFFC792EA); // class, const, final
|
||||
static const _synType = Color(0xFF78A0F8); // Widget, BuildContext
|
||||
static const _synString = Color(0xFFA8D99B);
|
||||
static const _synNumber = Color(0xFFE6C370);
|
||||
static const _synComment = Color(0xFF545C84);
|
||||
static const _synMethod = Color(0xFF82B1FF);
|
||||
static const _synPunct = Color(0xFF78809C);
|
||||
static const _synKeyword = Color(0xFFC792EA); // class, const, final
|
||||
static const _synType = Color(0xFF78A0F8); // Widget, BuildContext
|
||||
static const _synString = Color(0xFFA8D99B);
|
||||
static const _synNumber = Color(0xFFE6C370);
|
||||
static const _synComment = Color(0xFF545C84);
|
||||
static const _synMethod = Color(0xFF82B1FF);
|
||||
static const _synPunct = Color(0xFF78809C);
|
||||
|
||||
// ─── exposed tokens ───────────────────────────────────────────────────
|
||||
static const tokens = ClideTokens(
|
||||
bg: _bg, bgSunken: _bgSunken, surface: _surface, surfaceHi: _surfaceHi,
|
||||
border: _border, borderHi: _borderHi,
|
||||
textHi: _textHi, text: _text, textDim: _textDim, textMute: _textMute,
|
||||
accent: _accent, accentPress: _accentPress, accentSoft: _accentSoft,
|
||||
ok: _ok, warn: _warn, err: _err, info: _info,
|
||||
synKeyword: _synKeyword, synType: _synType, synString: _synString,
|
||||
synNumber: _synNumber, synComment: _synComment, synMethod: _synMethod,
|
||||
bg: _bg,
|
||||
bgSunken: _bgSunken,
|
||||
surface: _surface,
|
||||
surfaceHi: _surfaceHi,
|
||||
border: _border,
|
||||
borderHi: _borderHi,
|
||||
textHi: _textHi,
|
||||
text: _text,
|
||||
textDim: _textDim,
|
||||
textMute: _textMute,
|
||||
accent: _accent,
|
||||
accentPress: _accentPress,
|
||||
accentSoft: _accentSoft,
|
||||
ok: _ok,
|
||||
warn: _warn,
|
||||
err: _err,
|
||||
info: _info,
|
||||
synKeyword: _synKeyword,
|
||||
synType: _synType,
|
||||
synString: _synString,
|
||||
synNumber: _synNumber,
|
||||
synComment: _synComment,
|
||||
synMethod: _synMethod,
|
||||
synPunct: _synPunct,
|
||||
);
|
||||
|
||||
// ─── ThemeData ────────────────────────────────────────────────────────
|
||||
static ThemeData get data => ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: _bg,
|
||||
canvasColor: _bg,
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: _bg,
|
||||
canvasColor: _bg,
|
||||
|
||||
colorScheme: const ColorScheme.dark(
|
||||
brightness: Brightness.dark,
|
||||
primary: _accent,
|
||||
onPrimary: Color(0xFF0D1020),
|
||||
secondary: _synKeyword,
|
||||
onSecondary: Color(0xFF0D1020),
|
||||
surface: _surface,
|
||||
onSurface: _textHi,
|
||||
surfaceContainerHighest: _surfaceHi,
|
||||
outline: _border,
|
||||
outlineVariant: _borderHi,
|
||||
error: _err,
|
||||
onError: Color(0xFF0D1020),
|
||||
),
|
||||
colorScheme: const ColorScheme.dark(
|
||||
brightness: Brightness.dark,
|
||||
primary: _accent,
|
||||
onPrimary: Color(0xFF0D1020),
|
||||
secondary: _synKeyword,
|
||||
onSecondary: Color(0xFF0D1020),
|
||||
surface: _surface,
|
||||
onSurface: _textHi,
|
||||
surfaceContainerHighest: _surfaceHi,
|
||||
outline: _border,
|
||||
outlineVariant: _borderHi,
|
||||
error: _err,
|
||||
onError: Color(0xFF0D1020),
|
||||
),
|
||||
|
||||
textTheme: const TextTheme(
|
||||
// Josefin Sans Light for display; JetBrains Mono for code/body.
|
||||
displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, height: 1.1, letterSpacing: 0.2, color: _textHi),
|
||||
displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, height: 1.15, letterSpacing: 0.2, color: _textHi),
|
||||
headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, height: 1.2, letterSpacing: 0.2, color: _textHi),
|
||||
titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, height: 1.3, letterSpacing: 0.3, color: _text),
|
||||
labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, height: 1.2, letterSpacing: 1.0, color: _textMute),
|
||||
bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi),
|
||||
bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, height: 1.4, color: _text),
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
// Josefin Sans Light for display; JetBrains Mono for code/body.
|
||||
displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, height: 1.1, letterSpacing: 0.2, color: _textHi),
|
||||
displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, height: 1.15, letterSpacing: 0.2, color: _textHi),
|
||||
headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, height: 1.2, letterSpacing: 0.2, color: _textHi),
|
||||
titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, height: 1.3, letterSpacing: 0.3, color: _text),
|
||||
labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, height: 1.2, letterSpacing: 1.0, color: _textMute),
|
||||
bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi),
|
||||
bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, height: 1.4, color: _text),
|
||||
),
|
||||
|
||||
dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1),
|
||||
dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1),
|
||||
|
||||
cardTheme: CardThemeData(
|
||||
color: _surface,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: const BorderSide(color: _border),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: _surface,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: const BorderSide(color: _border),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
),
|
||||
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: _bgSunken,
|
||||
hintStyle: const TextStyle(color: _textMute),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: const BorderSide(color: _border),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: const BorderSide(color: _border),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: const BorderSide(color: _accent, width: 1.5),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: _bgSunken,
|
||||
hintStyle: const TextStyle(color: _textMute),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: const BorderSide(color: _border),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: const BorderSide(color: _border),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: const BorderSide(color: _accent, width: 1.5),
|
||||
),
|
||||
),
|
||||
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _accent,
|
||||
foregroundColor: const Color(0xFF0D1020),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 12, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: _accent,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 12),
|
||||
),
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _accent,
|
||||
foregroundColor: const Color(0xFF0D1020),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 12, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: _accent,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 12),
|
||||
),
|
||||
),
|
||||
|
||||
// Pill-style chips (matches the Projects row in the dashboard).
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: _surface,
|
||||
side: const BorderSide(color: _borderHi),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
labelStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 11, color: _text),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
),
|
||||
// Pill-style chips (matches the Projects row in the dashboard).
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: _surface,
|
||||
side: const BorderSide(color: _borderHi),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
labelStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 11, color: _text),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
),
|
||||
|
||||
iconTheme: const IconThemeData(color: _textDim, size: 14),
|
||||
iconTheme: const IconThemeData(color: _textDim, size: 14),
|
||||
|
||||
tooltipTheme: TooltipThemeData(
|
||||
decoration: BoxDecoration(
|
||||
color: _surfaceHi,
|
||||
border: Border.all(color: _borderHi),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 11, color: _textHi),
|
||||
),
|
||||
tooltipTheme: TooltipThemeData(
|
||||
decoration: BoxDecoration(
|
||||
color: _surfaceHi,
|
||||
border: Border.all(color: _borderHi),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 11, color: _textHi),
|
||||
),
|
||||
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbColor: WidgetStatePropertyAll(_border),
|
||||
thickness: const WidgetStatePropertyAll(6),
|
||||
radius: const Radius.circular(3),
|
||||
),
|
||||
);
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbColor: WidgetStatePropertyAll(_border),
|
||||
thickness: const WidgetStatePropertyAll(6),
|
||||
radius: const Radius.circular(3),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Raw color tokens — use when Material widgets can't carry the meaning.
|
||||
class ClideTokens {
|
||||
const ClideTokens({
|
||||
required this.bg, required this.bgSunken,
|
||||
required this.surface, required this.surfaceHi,
|
||||
required this.border, required this.borderHi,
|
||||
required this.textHi, required this.text,
|
||||
required this.textDim, required this.textMute,
|
||||
required this.accent, required this.accentPress, required this.accentSoft,
|
||||
required this.ok, required this.warn, required this.err, required this.info,
|
||||
required this.synKeyword, required this.synType, required this.synString,
|
||||
required this.synNumber, required this.synComment,
|
||||
required this.synMethod, required this.synPunct,
|
||||
required this.bg,
|
||||
required this.bgSunken,
|
||||
required this.surface,
|
||||
required this.surfaceHi,
|
||||
required this.border,
|
||||
required this.borderHi,
|
||||
required this.textHi,
|
||||
required this.text,
|
||||
required this.textDim,
|
||||
required this.textMute,
|
||||
required this.accent,
|
||||
required this.accentPress,
|
||||
required this.accentSoft,
|
||||
required this.ok,
|
||||
required this.warn,
|
||||
required this.err,
|
||||
required this.info,
|
||||
required this.synKeyword,
|
||||
required this.synType,
|
||||
required this.synString,
|
||||
required this.synNumber,
|
||||
required this.synComment,
|
||||
required this.synMethod,
|
||||
required this.synPunct,
|
||||
});
|
||||
final Color bg, bgSunken, surface, surfaceHi;
|
||||
final Color border, borderHi;
|
||||
|
||||
@@ -6,69 +6,90 @@ import 'package:flutter/material.dart';
|
||||
import 'clide_theme.dart' show ClideTokens;
|
||||
|
||||
class MidnightTheme {
|
||||
static const _bg = Color(0xFF1E1E1E);
|
||||
static const _bgSunken = Color(0xFF181818);
|
||||
static const _surface = Color(0xFF252526);
|
||||
static const _surfaceHi = Color(0xFF2D2D2E);
|
||||
static const _border = Color(0xFF333333);
|
||||
static const _borderHi = Color(0xFF3F3F3F);
|
||||
static const _bg = Color(0xFF1E1E1E);
|
||||
static const _bgSunken = Color(0xFF181818);
|
||||
static const _surface = Color(0xFF252526);
|
||||
static const _surfaceHi = Color(0xFF2D2D2E);
|
||||
static const _border = Color(0xFF333333);
|
||||
static const _borderHi = Color(0xFF3F3F3F);
|
||||
|
||||
static const _textHi = Color(0xFFD4D4D4);
|
||||
static const _text = Color(0xFFBBBBBB);
|
||||
static const _textDim = Color(0xFF858585);
|
||||
static const _textMute = Color(0xFF6A6A6A);
|
||||
static const _textHi = Color(0xFFD4D4D4);
|
||||
static const _text = Color(0xFFBBBBBB);
|
||||
static const _textDim = Color(0xFF858585);
|
||||
static const _textMute = Color(0xFF6A6A6A);
|
||||
|
||||
static const _accent = Color(0xFF569CD6); // muted azure
|
||||
static const _accent = Color(0xFF569CD6); // muted azure
|
||||
static const _accentPress = Color(0xFF4785BD);
|
||||
static const _accentSoft = Color(0x22569CD6);
|
||||
static const _accentSoft = Color(0x22569CD6);
|
||||
|
||||
static const _ok = Color(0xFF89D185);
|
||||
static const _warn = Color(0xFFD7BA7D);
|
||||
static const _err = Color(0xFFF48771);
|
||||
static const _info = _accent;
|
||||
static const _ok = Color(0xFF89D185);
|
||||
static const _warn = Color(0xFFD7BA7D);
|
||||
static const _err = Color(0xFFF48771);
|
||||
static const _info = _accent;
|
||||
|
||||
static const _synKeyword = Color(0xFFC586C0);
|
||||
static const _synType = Color(0xFF4EC9B0);
|
||||
static const _synString = Color(0xFFCE9178);
|
||||
static const _synNumber = Color(0xFFB5CEA8);
|
||||
static const _synComment = Color(0xFF6A9955);
|
||||
static const _synMethod = Color(0xFFDCDCAA);
|
||||
static const _synPunct = Color(0xFF858585);
|
||||
static const _synKeyword = Color(0xFFC586C0);
|
||||
static const _synType = Color(0xFF4EC9B0);
|
||||
static const _synString = Color(0xFFCE9178);
|
||||
static const _synNumber = Color(0xFFB5CEA8);
|
||||
static const _synComment = Color(0xFF6A9955);
|
||||
static const _synMethod = Color(0xFFDCDCAA);
|
||||
static const _synPunct = Color(0xFF858585);
|
||||
|
||||
static const tokens = ClideTokens(
|
||||
bg: _bg, bgSunken: _bgSunken, surface: _surface, surfaceHi: _surfaceHi,
|
||||
border: _border, borderHi: _borderHi,
|
||||
textHi: _textHi, text: _text, textDim: _textDim, textMute: _textMute,
|
||||
accent: _accent, accentPress: _accentPress, accentSoft: _accentSoft,
|
||||
ok: _ok, warn: _warn, err: _err, info: _info,
|
||||
synKeyword: _synKeyword, synType: _synType, synString: _synString,
|
||||
synNumber: _synNumber, synComment: _synComment, synMethod: _synMethod,
|
||||
bg: _bg,
|
||||
bgSunken: _bgSunken,
|
||||
surface: _surface,
|
||||
surfaceHi: _surfaceHi,
|
||||
border: _border,
|
||||
borderHi: _borderHi,
|
||||
textHi: _textHi,
|
||||
text: _text,
|
||||
textDim: _textDim,
|
||||
textMute: _textMute,
|
||||
accent: _accent,
|
||||
accentPress: _accentPress,
|
||||
accentSoft: _accentSoft,
|
||||
ok: _ok,
|
||||
warn: _warn,
|
||||
err: _err,
|
||||
info: _info,
|
||||
synKeyword: _synKeyword,
|
||||
synType: _synType,
|
||||
synString: _synString,
|
||||
synNumber: _synNumber,
|
||||
synComment: _synComment,
|
||||
synMethod: _synMethod,
|
||||
synPunct: _synPunct,
|
||||
);
|
||||
|
||||
static ThemeData get data => ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: _bg,
|
||||
canvasColor: _bg,
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: _accent, onPrimary: Color(0xFF0B1220),
|
||||
secondary: _synKeyword, onSecondary: Color(0xFF0B1220),
|
||||
surface: _surface, onSurface: _textHi,
|
||||
surfaceContainerHighest: _surfaceHi,
|
||||
outline: _border, outlineVariant: _borderHi,
|
||||
error: _err, onError: Color(0xFF0B1220),
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi),
|
||||
displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi),
|
||||
headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi),
|
||||
titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text),
|
||||
labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textMute),
|
||||
bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi),
|
||||
bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1),
|
||||
iconTheme: const IconThemeData(color: _textDim, size: 14),
|
||||
);
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: _bg,
|
||||
canvasColor: _bg,
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: _accent,
|
||||
onPrimary: Color(0xFF0B1220),
|
||||
secondary: _synKeyword,
|
||||
onSecondary: Color(0xFF0B1220),
|
||||
surface: _surface,
|
||||
onSurface: _textHi,
|
||||
surfaceContainerHighest: _surfaceHi,
|
||||
outline: _border,
|
||||
outlineVariant: _borderHi,
|
||||
error: _err,
|
||||
onError: Color(0xFF0B1220),
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi),
|
||||
displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi),
|
||||
headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi),
|
||||
titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text),
|
||||
labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textMute),
|
||||
bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi),
|
||||
bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1),
|
||||
iconTheme: const IconThemeData(color: _textDim, size: 14),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,70 +7,91 @@ import 'package:flutter/material.dart';
|
||||
import 'clide_theme.dart' show ClideTokens;
|
||||
|
||||
class PaperTheme {
|
||||
static const _bg = Color(0xFFF4F1EA); // paper
|
||||
static const _bgSunken = Color(0xFFECE7DB); // paper-2
|
||||
static const _surface = Color(0xFFFBF8F1);
|
||||
static const _surfaceHi = Color(0xFFECE7DB);
|
||||
static const _border = Color(0xFF1A1A1A);
|
||||
static const _borderHi = Color(0xFF4A4A4A);
|
||||
static const _bg = Color(0xFFF4F1EA); // paper
|
||||
static const _bgSunken = Color(0xFFECE7DB); // paper-2
|
||||
static const _surface = Color(0xFFFBF8F1);
|
||||
static const _surfaceHi = Color(0xFFECE7DB);
|
||||
static const _border = Color(0xFF1A1A1A);
|
||||
static const _borderHi = Color(0xFF4A4A4A);
|
||||
|
||||
static const _textHi = Color(0xFF1A1A1A); // ink
|
||||
static const _text = Color(0xFF4A4A4A); // ink-2
|
||||
static const _textDim = Color(0xFF8A8A82); // ink-3
|
||||
static const _textMute = Color(0xFFA8A89E);
|
||||
static const _textHi = Color(0xFF1A1A1A); // ink
|
||||
static const _text = Color(0xFF4A4A4A); // ink-2
|
||||
static const _textDim = Color(0xFF8A8A82); // ink-3
|
||||
static const _textMute = Color(0xFFA8A89E);
|
||||
|
||||
static const _accent = Color(0xFFC14B2A); // red pencil
|
||||
static const _accent = Color(0xFFC14B2A); // red pencil
|
||||
static const _accentPress = Color(0xFFA03D20);
|
||||
static const _accentSoft = Color(0x22C14B2A);
|
||||
static const _accentSoft = Color(0x22C14B2A);
|
||||
|
||||
static const _ok = Color(0xFF2D8A52);
|
||||
static const _warn = Color(0xFFB88A2A);
|
||||
static const _err = Color(0xFFB03A2A);
|
||||
static const _info = Color(0xFF2A6FC1);
|
||||
static const _ok = Color(0xFF2D8A52);
|
||||
static const _warn = Color(0xFFB88A2A);
|
||||
static const _err = Color(0xFFB03A2A);
|
||||
static const _info = Color(0xFF2A6FC1);
|
||||
|
||||
// Syntax tuned for cream paper — desaturated so code reads like print.
|
||||
static const _synKeyword = Color(0xFF7B3F8C);
|
||||
static const _synType = Color(0xFF2A6FC1);
|
||||
static const _synString = Color(0xFF2D8A52);
|
||||
static const _synNumber = Color(0xFFB88A2A);
|
||||
static const _synComment = Color(0xFF8A8A82);
|
||||
static const _synMethod = Color(0xFF1E5D9E);
|
||||
static const _synPunct = Color(0xFF4A4A4A);
|
||||
static const _synKeyword = Color(0xFF7B3F8C);
|
||||
static const _synType = Color(0xFF2A6FC1);
|
||||
static const _synString = Color(0xFF2D8A52);
|
||||
static const _synNumber = Color(0xFFB88A2A);
|
||||
static const _synComment = Color(0xFF8A8A82);
|
||||
static const _synMethod = Color(0xFF1E5D9E);
|
||||
static const _synPunct = Color(0xFF4A4A4A);
|
||||
|
||||
static const tokens = ClideTokens(
|
||||
bg: _bg, bgSunken: _bgSunken, surface: _surface, surfaceHi: _surfaceHi,
|
||||
border: _border, borderHi: _borderHi,
|
||||
textHi: _textHi, text: _text, textDim: _textDim, textMute: _textMute,
|
||||
accent: _accent, accentPress: _accentPress, accentSoft: _accentSoft,
|
||||
ok: _ok, warn: _warn, err: _err, info: _info,
|
||||
synKeyword: _synKeyword, synType: _synType, synString: _synString,
|
||||
synNumber: _synNumber, synComment: _synComment, synMethod: _synMethod,
|
||||
bg: _bg,
|
||||
bgSunken: _bgSunken,
|
||||
surface: _surface,
|
||||
surfaceHi: _surfaceHi,
|
||||
border: _border,
|
||||
borderHi: _borderHi,
|
||||
textHi: _textHi,
|
||||
text: _text,
|
||||
textDim: _textDim,
|
||||
textMute: _textMute,
|
||||
accent: _accent,
|
||||
accentPress: _accentPress,
|
||||
accentSoft: _accentSoft,
|
||||
ok: _ok,
|
||||
warn: _warn,
|
||||
err: _err,
|
||||
info: _info,
|
||||
synKeyword: _synKeyword,
|
||||
synType: _synType,
|
||||
synString: _synString,
|
||||
synNumber: _synNumber,
|
||||
synComment: _synComment,
|
||||
synMethod: _synMethod,
|
||||
synPunct: _synPunct,
|
||||
);
|
||||
|
||||
static ThemeData get data => ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: _bg,
|
||||
canvasColor: _bg,
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: _accent, onPrimary: Color(0xFFFBF8F1),
|
||||
secondary: _info, onSecondary: Color(0xFFFBF8F1),
|
||||
surface: _surface, onSurface: _textHi,
|
||||
surfaceContainerHighest: _surfaceHi,
|
||||
outline: _border, outlineVariant: _borderHi,
|
||||
error: _err, onError: Color(0xFFFBF8F1),
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi),
|
||||
displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi),
|
||||
headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi),
|
||||
titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text),
|
||||
labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textDim),
|
||||
bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi),
|
||||
bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1),
|
||||
iconTheme: const IconThemeData(color: _text, size: 14),
|
||||
);
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: _bg,
|
||||
canvasColor: _bg,
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: _accent,
|
||||
onPrimary: Color(0xFFFBF8F1),
|
||||
secondary: _info,
|
||||
onSecondary: Color(0xFFFBF8F1),
|
||||
surface: _surface,
|
||||
onSurface: _textHi,
|
||||
surfaceContainerHighest: _surfaceHi,
|
||||
outline: _border,
|
||||
outlineVariant: _borderHi,
|
||||
error: _err,
|
||||
onError: Color(0xFFFBF8F1),
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi),
|
||||
displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi),
|
||||
headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi),
|
||||
titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text),
|
||||
labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textDim),
|
||||
bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi),
|
||||
bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1),
|
||||
iconTheme: const IconThemeData(color: _text, size: 14),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,71 +7,92 @@ import 'package:flutter/material.dart';
|
||||
import 'clide_theme.dart' show ClideTokens;
|
||||
|
||||
class TerminalTheme {
|
||||
static const _bg = Color(0xFF0A0A0A);
|
||||
static const _bgSunken = Color(0xFF000000);
|
||||
static const _surface = Color(0xFF111111);
|
||||
static const _surfaceHi = Color(0xFF181818);
|
||||
static const _border = Color(0xFF242424);
|
||||
static const _borderHi = Color(0xFF2E2E2E);
|
||||
static const _bg = Color(0xFF0A0A0A);
|
||||
static const _bgSunken = Color(0xFF000000);
|
||||
static const _surface = Color(0xFF111111);
|
||||
static const _surfaceHi = Color(0xFF181818);
|
||||
static const _border = Color(0xFF242424);
|
||||
static const _borderHi = Color(0xFF2E2E2E);
|
||||
|
||||
static const _textHi = Color(0xFFE6E6E6);
|
||||
static const _text = Color(0xFFBDBDBD);
|
||||
static const _textDim = Color(0xFF7A7A7A);
|
||||
static const _textMute = Color(0xFF4A4A4A);
|
||||
static const _textHi = Color(0xFFE6E6E6);
|
||||
static const _text = Color(0xFFBDBDBD);
|
||||
static const _textDim = Color(0xFF7A7A7A);
|
||||
static const _textMute = Color(0xFF4A4A4A);
|
||||
|
||||
static const _accent = Color(0xFFE0B050); // amber
|
||||
static const _accent = Color(0xFFE0B050); // amber
|
||||
static const _accentPress = Color(0xFFC29438);
|
||||
static const _accentSoft = Color(0x22E0B050);
|
||||
static const _accentSoft = Color(0x22E0B050);
|
||||
|
||||
static const _ok = Color(0xFF8FDC9B);
|
||||
static const _warn = Color(0xFFE0B050);
|
||||
static const _err = Color(0xFFE05050);
|
||||
static const _info = Color(0xFFA3C4FF);
|
||||
static const _ok = Color(0xFF8FDC9B);
|
||||
static const _warn = Color(0xFFE0B050);
|
||||
static const _err = Color(0xFFE05050);
|
||||
static const _info = Color(0xFFA3C4FF);
|
||||
|
||||
// Classic 16-color palette feel
|
||||
static const _synKeyword = Color(0xFFE05050);
|
||||
static const _synType = Color(0xFFE0B050);
|
||||
static const _synString = Color(0xFF8FDC9B);
|
||||
static const _synNumber = Color(0xFFC792EA);
|
||||
static const _synComment = Color(0xFF4A4A4A);
|
||||
static const _synMethod = Color(0xFFA3C4FF);
|
||||
static const _synPunct = Color(0xFF7A7A7A);
|
||||
static const _synKeyword = Color(0xFFE05050);
|
||||
static const _synType = Color(0xFFE0B050);
|
||||
static const _synString = Color(0xFF8FDC9B);
|
||||
static const _synNumber = Color(0xFFC792EA);
|
||||
static const _synComment = Color(0xFF4A4A4A);
|
||||
static const _synMethod = Color(0xFFA3C4FF);
|
||||
static const _synPunct = Color(0xFF7A7A7A);
|
||||
|
||||
static const tokens = ClideTokens(
|
||||
bg: _bg, bgSunken: _bgSunken, surface: _surface, surfaceHi: _surfaceHi,
|
||||
border: _border, borderHi: _borderHi,
|
||||
textHi: _textHi, text: _text, textDim: _textDim, textMute: _textMute,
|
||||
accent: _accent, accentPress: _accentPress, accentSoft: _accentSoft,
|
||||
ok: _ok, warn: _warn, err: _err, info: _info,
|
||||
synKeyword: _synKeyword, synType: _synType, synString: _synString,
|
||||
synNumber: _synNumber, synComment: _synComment, synMethod: _synMethod,
|
||||
bg: _bg,
|
||||
bgSunken: _bgSunken,
|
||||
surface: _surface,
|
||||
surfaceHi: _surfaceHi,
|
||||
border: _border,
|
||||
borderHi: _borderHi,
|
||||
textHi: _textHi,
|
||||
text: _text,
|
||||
textDim: _textDim,
|
||||
textMute: _textMute,
|
||||
accent: _accent,
|
||||
accentPress: _accentPress,
|
||||
accentSoft: _accentSoft,
|
||||
ok: _ok,
|
||||
warn: _warn,
|
||||
err: _err,
|
||||
info: _info,
|
||||
synKeyword: _synKeyword,
|
||||
synType: _synType,
|
||||
synString: _synString,
|
||||
synNumber: _synNumber,
|
||||
synComment: _synComment,
|
||||
synMethod: _synMethod,
|
||||
synPunct: _synPunct,
|
||||
);
|
||||
|
||||
static ThemeData get data => ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: _bg,
|
||||
canvasColor: _bg,
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: _accent, onPrimary: Color(0xFF000000),
|
||||
secondary: _info, onSecondary: Color(0xFF000000),
|
||||
surface: _surface, onSurface: _textHi,
|
||||
surfaceContainerHighest: _surfaceHi,
|
||||
outline: _border, outlineVariant: _borderHi,
|
||||
error: _err, onError: Color(0xFF000000),
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi),
|
||||
displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi),
|
||||
headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi),
|
||||
titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text),
|
||||
// Terminal mockups go full-mono even for display.
|
||||
labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textMute),
|
||||
bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi),
|
||||
bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1),
|
||||
iconTheme: const IconThemeData(color: _textDim, size: 14),
|
||||
);
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: _bg,
|
||||
canvasColor: _bg,
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: _accent,
|
||||
onPrimary: Color(0xFF000000),
|
||||
secondary: _info,
|
||||
onSecondary: Color(0xFF000000),
|
||||
surface: _surface,
|
||||
onSurface: _textHi,
|
||||
surfaceContainerHighest: _surfaceHi,
|
||||
outline: _border,
|
||||
outlineVariant: _borderHi,
|
||||
error: _err,
|
||||
onError: Color(0xFF000000),
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi),
|
||||
displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi),
|
||||
headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi),
|
||||
titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text),
|
||||
// Terminal mockups go full-mono even for display.
|
||||
labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textMute),
|
||||
bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi),
|
||||
bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text),
|
||||
),
|
||||
dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1),
|
||||
iconTheme: const IconThemeData(color: _textDim, size: 14),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,33 +72,33 @@ class ClideThemes {
|
||||
dark: true,
|
||||
subtitle: 'cool near-black + periwinkle · default',
|
||||
palette: ClidePalette(
|
||||
bg: Color(0xFF20202C),
|
||||
bgSunken: Color(0xFF1A1A24),
|
||||
surface: Color(0xFF242838),
|
||||
surfaceHi: Color(0xFF2C3046),
|
||||
border: Color(0xFF343850),
|
||||
borderHi: Color(0xFF3C445C),
|
||||
textHi: Color(0xFFE6E8F2),
|
||||
text: Color(0xFFB1BBE3),
|
||||
textDim: Color(0xFF78809C),
|
||||
textMute: Color(0xFF545C84),
|
||||
accent: Color(0xFF78A0F8),
|
||||
bg: Color(0xFF20202C),
|
||||
bgSunken: Color(0xFF1A1A24),
|
||||
surface: Color(0xFF242838),
|
||||
surfaceHi: Color(0xFF2C3046),
|
||||
border: Color(0xFF343850),
|
||||
borderHi: Color(0xFF3C445C),
|
||||
textHi: Color(0xFFE6E8F2),
|
||||
text: Color(0xFFB1BBE3),
|
||||
textDim: Color(0xFF78809C),
|
||||
textMute: Color(0xFF545C84),
|
||||
accent: Color(0xFF78A0F8),
|
||||
accentPress: Color(0xFF6C90DC),
|
||||
accentSoft: Color(0x2178A0F8),
|
||||
onAccent: Color(0xFF0D1020),
|
||||
ok: Color(0xFF7DD3A8),
|
||||
warn: Color(0xFFE6C370),
|
||||
err: Color(0xFFE87D7D),
|
||||
info: Color(0xFF78A0F8),
|
||||
accentSoft: Color(0x2178A0F8),
|
||||
onAccent: Color(0xFF0D1020),
|
||||
ok: Color(0xFF7DD3A8),
|
||||
warn: Color(0xFFE6C370),
|
||||
err: Color(0xFFE87D7D),
|
||||
info: Color(0xFF78A0F8),
|
||||
),
|
||||
syntax: ClideSyntax(
|
||||
keyword: Color(0xFFC792EA),
|
||||
type: Color(0xFF78A0F8),
|
||||
string: Color(0xFFA8D99B),
|
||||
number: Color(0xFFE6C370),
|
||||
comment: Color(0xFF545C84),
|
||||
method: Color(0xFF82B1FF),
|
||||
punct: Color(0xFF78809C),
|
||||
keyword: Color(0xFFC792EA),
|
||||
type: Color(0xFF78A0F8),
|
||||
string: Color(0xFFA8D99B),
|
||||
number: Color(0xFFE6C370),
|
||||
comment: Color(0xFF545C84),
|
||||
method: Color(0xFF82B1FF),
|
||||
punct: Color(0xFF78809C),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -108,33 +108,33 @@ class ClideThemes {
|
||||
dark: true,
|
||||
subtitle: 'VS Code-adjacent muted dark',
|
||||
palette: ClidePalette(
|
||||
bg: Color(0xFF1E1E1E),
|
||||
bgSunken: Color(0xFF181818),
|
||||
surface: Color(0xFF252526),
|
||||
surfaceHi: Color(0xFF2D2D2E),
|
||||
border: Color(0xFF333333),
|
||||
borderHi: Color(0xFF3F3F3F),
|
||||
textHi: Color(0xFFD4D4D4),
|
||||
text: Color(0xFFBBBBBB),
|
||||
textDim: Color(0xFF858585),
|
||||
textMute: Color(0xFF6A6A6A),
|
||||
accent: Color(0xFF569CD6),
|
||||
bg: Color(0xFF1E1E1E),
|
||||
bgSunken: Color(0xFF181818),
|
||||
surface: Color(0xFF252526),
|
||||
surfaceHi: Color(0xFF2D2D2E),
|
||||
border: Color(0xFF333333),
|
||||
borderHi: Color(0xFF3F3F3F),
|
||||
textHi: Color(0xFFD4D4D4),
|
||||
text: Color(0xFFBBBBBB),
|
||||
textDim: Color(0xFF858585),
|
||||
textMute: Color(0xFF6A6A6A),
|
||||
accent: Color(0xFF569CD6),
|
||||
accentPress: Color(0xFF4785BD),
|
||||
accentSoft: Color(0x21569CD6),
|
||||
onAccent: Color(0xFF0B1220),
|
||||
ok: Color(0xFF89D185),
|
||||
warn: Color(0xFFD7BA7D),
|
||||
err: Color(0xFFF48771),
|
||||
info: Color(0xFF569CD6),
|
||||
accentSoft: Color(0x21569CD6),
|
||||
onAccent: Color(0xFF0B1220),
|
||||
ok: Color(0xFF89D185),
|
||||
warn: Color(0xFFD7BA7D),
|
||||
err: Color(0xFFF48771),
|
||||
info: Color(0xFF569CD6),
|
||||
),
|
||||
syntax: ClideSyntax(
|
||||
keyword: Color(0xFFC586C0),
|
||||
type: Color(0xFF4EC9B0),
|
||||
string: Color(0xFFCE9178),
|
||||
number: Color(0xFFB5CEA8),
|
||||
comment: Color(0xFF6A9955),
|
||||
method: Color(0xFFDCDCAA),
|
||||
punct: Color(0xFF858585),
|
||||
keyword: Color(0xFFC586C0),
|
||||
type: Color(0xFF4EC9B0),
|
||||
string: Color(0xFFCE9178),
|
||||
number: Color(0xFFB5CEA8),
|
||||
comment: Color(0xFF6A9955),
|
||||
method: Color(0xFFDCDCAA),
|
||||
punct: Color(0xFF858585),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -144,33 +144,33 @@ class ClideThemes {
|
||||
dark: false,
|
||||
subtitle: 'drafting sheet · red-pencil accent · light',
|
||||
palette: ClidePalette(
|
||||
bg: Color(0xFFF4F1EA),
|
||||
bgSunken: Color(0xFFECE7DB),
|
||||
surface: Color(0xFFFBF8F1),
|
||||
surfaceHi: Color(0xFFECE7DB),
|
||||
border: Color(0xFF1A1A1A),
|
||||
borderHi: Color(0xFF4A4A4A),
|
||||
textHi: Color(0xFF1A1A1A),
|
||||
text: Color(0xFF4A4A4A),
|
||||
textDim: Color(0xFF8A8A82),
|
||||
textMute: Color(0xFFA8A89E),
|
||||
accent: Color(0xFFC14B2A),
|
||||
bg: Color(0xFFF4F1EA),
|
||||
bgSunken: Color(0xFFECE7DB),
|
||||
surface: Color(0xFFFBF8F1),
|
||||
surfaceHi: Color(0xFFECE7DB),
|
||||
border: Color(0xFF1A1A1A),
|
||||
borderHi: Color(0xFF4A4A4A),
|
||||
textHi: Color(0xFF1A1A1A),
|
||||
text: Color(0xFF4A4A4A),
|
||||
textDim: Color(0xFF8A8A82),
|
||||
textMute: Color(0xFFA8A89E),
|
||||
accent: Color(0xFFC14B2A),
|
||||
accentPress: Color(0xFFA03D20),
|
||||
accentSoft: Color(0x21C14B2A),
|
||||
onAccent: Color(0xFFFBF8F1),
|
||||
ok: Color(0xFF2D8A52),
|
||||
warn: Color(0xFFB88A2A),
|
||||
err: Color(0xFFB03A2A),
|
||||
info: Color(0xFF2A6FC1),
|
||||
accentSoft: Color(0x21C14B2A),
|
||||
onAccent: Color(0xFFFBF8F1),
|
||||
ok: Color(0xFF2D8A52),
|
||||
warn: Color(0xFFB88A2A),
|
||||
err: Color(0xFFB03A2A),
|
||||
info: Color(0xFF2A6FC1),
|
||||
),
|
||||
syntax: ClideSyntax(
|
||||
keyword: Color(0xFF7B3F8C),
|
||||
type: Color(0xFF2A6FC1),
|
||||
string: Color(0xFF2D8A52),
|
||||
number: Color(0xFFB88A2A),
|
||||
comment: Color(0xFF8A8A82),
|
||||
method: Color(0xFF1E5D9E),
|
||||
punct: Color(0xFF4A4A4A),
|
||||
keyword: Color(0xFF7B3F8C),
|
||||
type: Color(0xFF2A6FC1),
|
||||
string: Color(0xFF2D8A52),
|
||||
number: Color(0xFFB88A2A),
|
||||
comment: Color(0xFF8A8A82),
|
||||
method: Color(0xFF1E5D9E),
|
||||
punct: Color(0xFF4A4A4A),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -180,33 +180,33 @@ class ClideThemes {
|
||||
dark: true,
|
||||
subtitle: 'near-black + amber · tmux feel',
|
||||
palette: ClidePalette(
|
||||
bg: Color(0xFF0A0A0A),
|
||||
bgSunken: Color(0xFF000000),
|
||||
surface: Color(0xFF111111),
|
||||
surfaceHi: Color(0xFF181818),
|
||||
border: Color(0xFF242424),
|
||||
borderHi: Color(0xFF2E2E2E),
|
||||
textHi: Color(0xFFE6E6E6),
|
||||
text: Color(0xFFBDBDBD),
|
||||
textDim: Color(0xFF7A7A7A),
|
||||
textMute: Color(0xFF4A4A4A),
|
||||
accent: Color(0xFFE0B050),
|
||||
bg: Color(0xFF0A0A0A),
|
||||
bgSunken: Color(0xFF000000),
|
||||
surface: Color(0xFF111111),
|
||||
surfaceHi: Color(0xFF181818),
|
||||
border: Color(0xFF242424),
|
||||
borderHi: Color(0xFF2E2E2E),
|
||||
textHi: Color(0xFFE6E6E6),
|
||||
text: Color(0xFFBDBDBD),
|
||||
textDim: Color(0xFF7A7A7A),
|
||||
textMute: Color(0xFF4A4A4A),
|
||||
accent: Color(0xFFE0B050),
|
||||
accentPress: Color(0xFFC29438),
|
||||
accentSoft: Color(0x21E0B050),
|
||||
onAccent: Color(0xFF000000),
|
||||
ok: Color(0xFF8FDC9B),
|
||||
warn: Color(0xFFE0B050),
|
||||
err: Color(0xFFE05050),
|
||||
info: Color(0xFFA3C4FF),
|
||||
accentSoft: Color(0x21E0B050),
|
||||
onAccent: Color(0xFF000000),
|
||||
ok: Color(0xFF8FDC9B),
|
||||
warn: Color(0xFFE0B050),
|
||||
err: Color(0xFFE05050),
|
||||
info: Color(0xFFA3C4FF),
|
||||
),
|
||||
syntax: ClideSyntax(
|
||||
keyword: Color(0xFFE05050),
|
||||
type: Color(0xFFE0B050),
|
||||
string: Color(0xFF8FDC9B),
|
||||
number: Color(0xFFC792EA),
|
||||
comment: Color(0xFF4A4A4A),
|
||||
method: Color(0xFFA3C4FF),
|
||||
punct: Color(0xFF7A7A7A),
|
||||
keyword: Color(0xFFE05050),
|
||||
type: Color(0xFFE0B050),
|
||||
string: Color(0xFF8FDC9B),
|
||||
number: Color(0xFFC792EA),
|
||||
comment: Color(0xFF4A4A4A),
|
||||
method: Color(0xFFA3C4FF),
|
||||
punct: Color(0xFF7A7A7A),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -21,9 +21,7 @@ import '../test/helpers/fake_ipc.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets(
|
||||
'clide app boots with classic 3-column layout + welcome + statusbar',
|
||||
(tester) async {
|
||||
testWidgets('clide app boots with classic 3-column layout + welcome + statusbar', (tester) async {
|
||||
final themes = [
|
||||
await const ThemeLoader().fromAsset(
|
||||
rootBundle,
|
||||
@@ -40,8 +38,7 @@ void main() {
|
||||
'builtin.theme-picker',
|
||||
'builtin.default-layout',
|
||||
],
|
||||
daemonClientFactory: (log, events) =>
|
||||
FakeDaemonClient(log: log, events: events),
|
||||
daemonClientFactory: (log, events) => FakeDaemonClient(log: log, events: events),
|
||||
autoStartDaemonClient: false,
|
||||
);
|
||||
services.extensions
|
||||
|
||||
@@ -14,8 +14,7 @@ import '../test/helpers/fake_ipc.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('disable + re-enable an extension mounts/unmounts its UI',
|
||||
(tester) async {
|
||||
testWidgets('disable + re-enable an extension mounts/unmounts its UI', (tester) async {
|
||||
final themes = [
|
||||
await const ThemeLoader().fromAsset(
|
||||
rootBundle,
|
||||
@@ -31,8 +30,7 @@ void main() {
|
||||
'builtin.ipc-status',
|
||||
'builtin.default-layout',
|
||||
],
|
||||
daemonClientFactory: (log, events) =>
|
||||
FakeDaemonClient(log: log, events: events),
|
||||
daemonClientFactory: (log, events) => FakeDaemonClient(log: log, events: events),
|
||||
autoStartDaemonClient: false,
|
||||
);
|
||||
services.extensions
|
||||
|
||||
@@ -14,8 +14,7 @@ import '../test/helpers/fake_ipc.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('theme.pick command opens modal; selecting dismisses it',
|
||||
(tester) async {
|
||||
testWidgets('theme.pick command opens modal; selecting dismisses it', (tester) async {
|
||||
final themes = [
|
||||
await const ThemeLoader().fromAsset(
|
||||
rootBundle,
|
||||
@@ -31,8 +30,7 @@ void main() {
|
||||
'builtin.theme-picker',
|
||||
'builtin.default-layout',
|
||||
],
|
||||
daemonClientFactory: (log, events) =>
|
||||
FakeDaemonClient(log: log, events: events),
|
||||
daemonClientFactory: (log, events) => FakeDaemonClient(log: log, events: events),
|
||||
autoStartDaemonClient: false,
|
||||
);
|
||||
services.extensions
|
||||
|
||||
@@ -109,7 +109,13 @@ class _TabRow extends StatelessWidget {
|
||||
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: tokens.dividerColor))),
|
||||
child: Row(
|
||||
children: [
|
||||
for (var i = 0; i < sessions.length; i++) _Tab(session: sessions[i], active: i == activeIndex, tokens: tokens, onTap: () => onSelect(i), onClose: sessions[i].isPrimary ? null : () => onClose(i)),
|
||||
for (var i = 0; i < sessions.length; i++)
|
||||
_Tab(
|
||||
session: sessions[i],
|
||||
active: i == activeIndex,
|
||||
tokens: tokens,
|
||||
onTap: () => onSelect(i),
|
||||
onClose: sessions[i].isPrimary ? null : () => onClose(i)),
|
||||
const SizedBox(width: 4),
|
||||
_AddButton(tokens: tokens, onTap: onAdd),
|
||||
const Spacer(),
|
||||
|
||||
@@ -30,6 +30,5 @@ class DecisionTypeColors {
|
||||
rejected: Color(0xFFC03030),
|
||||
);
|
||||
|
||||
static DecisionTypeColors forTheme({required bool dark}) =>
|
||||
dark ? DecisionTypeColors.dark : DecisionTypeColors.light;
|
||||
static DecisionTypeColors forTheme({required bool dark}) => dark ? DecisionTypeColors.dark : DecisionTypeColors.light;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,10 @@ class _DecisionsViewState extends State<DecisionsView> {
|
||||
if (_focusSub == null) {
|
||||
final kernel = ClideKernel.of(context);
|
||||
_focusSub = kernel.messages.subscribe(publisher: 'builtin.decisions', channel: 'focus').listen(_onFocus);
|
||||
_fileSub = kernel.events.on<DaemonEvent>().where((e) => e.subsystem == 'files' && e.kind == 'files.changed' && _isDecisionPath(e.data['path'] as String? ?? '')).listen((_) => _refresh());
|
||||
_fileSub = kernel.events
|
||||
.on<DaemonEvent>()
|
||||
.where((e) => e.subsystem == 'files' && e.kind == 'files.changed' && _isDecisionPath(e.data['path'] as String? ?? ''))
|
||||
.listen((_) => _refresh());
|
||||
_schedulerSub = kernel.events.on<SchedulerTick>().where((e) => e.tier == SchedulerTier.oneMinute).listen((_) => _refresh());
|
||||
}
|
||||
if (!_loading || _decisions.isNotEmpty) return;
|
||||
@@ -43,7 +46,10 @@ class _DecisionsViewState extends State<DecisionsView> {
|
||||
|
||||
Future<void> _refresh() async {
|
||||
if (!mounted) return;
|
||||
if (_refreshing) { _pendingRefresh = true; return; }
|
||||
if (_refreshing) {
|
||||
_pendingRefresh = true;
|
||||
return;
|
||||
}
|
||||
_refreshing = true;
|
||||
_pendingRefresh = false;
|
||||
await _load();
|
||||
@@ -117,11 +123,20 @@ class _DecisionsViewState extends State<DecisionsView> {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
if (_loading) return const Center(child: ClideText('Loading decisions...', muted: true));
|
||||
if (_error != null) return Padding(padding: const EdgeInsets.all(12), child: ClideText(_error!, muted: true));
|
||||
if (_decisions.isEmpty) return const Padding(padding: EdgeInsets.all(12), child: ClideText('No decisions found.\nRun `pql decisions sync` to index.', muted: true));
|
||||
if (_decisions.isEmpty)
|
||||
return const Padding(padding: EdgeInsets.all(12), child: ClideText('No decisions found.\nRun `pql decisions sync` to index.', muted: true));
|
||||
|
||||
final lf = _filter.toLowerCase();
|
||||
final hasFilter = lf.isNotEmpty;
|
||||
final filtered = hasFilter ? _decisions.where((d) => d.id.toLowerCase().contains(lf) || d.title.toLowerCase().contains(lf) || (d.domain ?? '').toLowerCase().contains(lf) || (d.type ?? '').contains(lf)).toList() : _decisions;
|
||||
final filtered = hasFilter
|
||||
? _decisions
|
||||
.where((d) =>
|
||||
d.id.toLowerCase().contains(lf) ||
|
||||
d.title.toLowerCase().contains(lf) ||
|
||||
(d.domain ?? '').toLowerCase().contains(lf) ||
|
||||
(d.type ?? '').contains(lf))
|
||||
.toList()
|
||||
: _decisions;
|
||||
|
||||
final confirmed = filtered.where((d) => d.type == 'confirmed').toList();
|
||||
final questions = filtered.where((d) => d.type == 'question').toList();
|
||||
@@ -140,7 +155,8 @@ class _DecisionsViewState extends State<DecisionsView> {
|
||||
child: ClideTappable(
|
||||
onTap: _refreshing ? null : _refresh,
|
||||
tooltip: 'Refresh decisions',
|
||||
builder: (ctx, hovered, _) => ClideIcon(PhosphorIcons.arrowClockwise, size: 13, color: hovered ? tokens.globalForeground : tokens.globalTextMuted),
|
||||
builder: (ctx, hovered, _) =>
|
||||
ClideIcon(PhosphorIcons.arrowClockwise, size: 13, color: hovered ? tokens.globalForeground : tokens.globalTextMuted),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -151,27 +167,45 @@ class _DecisionsViewState extends State<DecisionsView> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (confirmed.isNotEmpty) ClideAccordion(
|
||||
label: 'CONFIRMED', count: confirmed.length,
|
||||
leading: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColors.confirmed, shape: BoxShape.circle)),
|
||||
expanded: hasFilter || _isSectionExpanded('confirmed'),
|
||||
onToggle: () => _toggleSection('confirmed'),
|
||||
children: [for (final d in confirmed) _DecisionCard(entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null)],
|
||||
),
|
||||
if (questions.isNotEmpty) ClideAccordion(
|
||||
label: 'QUESTIONS', count: questions.length,
|
||||
leading: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColors.question, shape: BoxShape.circle)),
|
||||
expanded: hasFilter || _isSectionExpanded('question'),
|
||||
onToggle: () => _toggleSection('question'),
|
||||
children: [for (final d in questions) _DecisionCard(entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null)],
|
||||
),
|
||||
if (rejected.isNotEmpty) ClideAccordion(
|
||||
label: 'REJECTED', count: rejected.length,
|
||||
leading: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColors.rejected, shape: BoxShape.circle)),
|
||||
expanded: hasFilter || _isSectionExpanded('rejected'),
|
||||
onToggle: () => _toggleSection('rejected'),
|
||||
children: [for (final d in rejected) _DecisionCard(entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null)],
|
||||
),
|
||||
if (confirmed.isNotEmpty)
|
||||
ClideAccordion(
|
||||
label: 'CONFIRMED',
|
||||
count: confirmed.length,
|
||||
leading: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColors.confirmed, shape: BoxShape.circle)),
|
||||
expanded: hasFilter || _isSectionExpanded('confirmed'),
|
||||
onToggle: () => _toggleSection('confirmed'),
|
||||
children: [
|
||||
for (final d in confirmed)
|
||||
_DecisionCard(
|
||||
entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null)
|
||||
],
|
||||
),
|
||||
if (questions.isNotEmpty)
|
||||
ClideAccordion(
|
||||
label: 'QUESTIONS',
|
||||
count: questions.length,
|
||||
leading: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColors.question, shape: BoxShape.circle)),
|
||||
expanded: hasFilter || _isSectionExpanded('question'),
|
||||
onToggle: () => _toggleSection('question'),
|
||||
children: [
|
||||
for (final d in questions)
|
||||
_DecisionCard(
|
||||
entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null)
|
||||
],
|
||||
),
|
||||
if (rejected.isNotEmpty)
|
||||
ClideAccordion(
|
||||
label: 'REJECTED',
|
||||
count: rejected.length,
|
||||
leading: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColors.rejected, shape: BoxShape.circle)),
|
||||
expanded: hasFilter || _isSectionExpanded('rejected'),
|
||||
onToggle: () => _toggleSection('rejected'),
|
||||
children: [
|
||||
for (final d in rejected)
|
||||
_DecisionCard(
|
||||
entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -233,8 +267,7 @@ class _DecisionCard extends StatelessWidget {
|
||||
const SizedBox(width: 6),
|
||||
ClideText(entry.id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
const Spacer(),
|
||||
if (entry.domain != null)
|
||||
ClideText(entry.domain!, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
if (entry.domain != null) ClideText(entry.domain!, fontSize: clideFontBadge, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
@@ -68,9 +68,7 @@ class _DiffViewState extends State<DiffView> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ClideText(
|
||||
c.showStaged
|
||||
? 'No staged changes.'
|
||||
: 'No unstaged changes.',
|
||||
c.showStaged ? 'No staged changes.' : 'No unstaged changes.',
|
||||
muted: true,
|
||||
),
|
||||
),
|
||||
@@ -81,8 +79,7 @@ class _DiffViewState extends State<DiffView> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (final diff in c.diffs)
|
||||
_FileDiff(diff: diff, controller: c),
|
||||
for (final diff in c.diffs) _FileDiff(diff: diff, controller: c),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -118,9 +115,7 @@ class _DiffToolbar extends StatelessWidget {
|
||||
child: ClideText(
|
||||
'Unstaged',
|
||||
fontSize: clideFontCaption,
|
||||
color: controller.showStaged
|
||||
? tokens.globalTextMuted
|
||||
: tokens.globalForeground,
|
||||
color: controller.showStaged ? tokens.globalTextMuted : tokens.globalForeground,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -134,9 +129,7 @@ class _DiffToolbar extends StatelessWidget {
|
||||
child: ClideText(
|
||||
'Staged',
|
||||
fontSize: clideFontCaption,
|
||||
color: controller.showStaged
|
||||
? tokens.globalForeground
|
||||
: tokens.globalTextMuted,
|
||||
color: controller.showStaged ? tokens.globalForeground : tokens.globalTextMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -188,12 +181,8 @@ class _FileDiff extends StatelessWidget {
|
||||
color: tokens.panelHeaderForeground,
|
||||
),
|
||||
),
|
||||
if (additions > 0)
|
||||
ClideText('+$additions ', fontSize: clideFontCaption,
|
||||
color: tokens.statusSuccess),
|
||||
if (removals > 0)
|
||||
ClideText('-$removals', fontSize: clideFontCaption,
|
||||
color: tokens.statusError),
|
||||
if (additions > 0) ClideText('+$additions ', fontSize: clideFontCaption, color: tokens.statusSuccess),
|
||||
if (removals > 0) ClideText('-$removals', fontSize: clideFontCaption, color: tokens.statusError),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -17,8 +17,7 @@ import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class EditorController extends ChangeNotifier {
|
||||
EditorController({required this.ipc, required DaemonBus events})
|
||||
: _events = events {
|
||||
EditorController({required this.ipc, required DaemonBus events}) : _events = events {
|
||||
_eventSub = events.on<DaemonEvent>().listen(_onEvent);
|
||||
}
|
||||
|
||||
@@ -77,9 +76,7 @@ class EditorController extends ChangeNotifier {
|
||||
_activePath = r.data['path']! as String;
|
||||
_content = (r.data['content'] as String?) ?? '';
|
||||
final sel = r.data['selection'];
|
||||
_selection = sel is Map
|
||||
? Selection.fromJson(sel.cast<String, Object?>())
|
||||
: const Selection.collapsed(0);
|
||||
_selection = sel is Map ? Selection.fromJson(sel.cast<String, Object?>()) : const Selection.collapsed(0);
|
||||
_dirty = (r.data['dirty'] as bool?) ?? false;
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
@@ -45,8 +45,7 @@ class _EditorViewState extends State<EditorView> {
|
||||
super.didChangeDependencies();
|
||||
if (_controller != null) return;
|
||||
final kernel = ClideKernel.of(context);
|
||||
_controller = EditorController(ipc: kernel.ipc, events: kernel.events)
|
||||
..addListener(_onControllerChanged);
|
||||
_controller = EditorController(ipc: kernel.ipc, events: kernel.events)..addListener(_onControllerChanged);
|
||||
unawaited(_controller!.hydrate());
|
||||
}
|
||||
|
||||
@@ -80,29 +79,22 @@ class _EditorViewState extends State<EditorView> {
|
||||
final c = _controller;
|
||||
if (c == null || c.activeId == null) return;
|
||||
final value = _text.value;
|
||||
if (value.text == c.content &&
|
||||
value.selection.baseOffset == c.selection.start &&
|
||||
value.selection.extentOffset == c.selection.end) {
|
||||
if (value.text == c.content && value.selection.baseOffset == c.selection.start && value.selection.extentOffset == c.selection.end) {
|
||||
return;
|
||||
}
|
||||
_lastRemoteContent = value.text;
|
||||
c.pushLocalEdit(
|
||||
newContent: value.text,
|
||||
newSelection: Selection(
|
||||
start: value.selection.start < 0
|
||||
? value.text.length
|
||||
: value.selection.start,
|
||||
end: value.selection.end < 0
|
||||
? value.text.length
|
||||
: value.selection.end,
|
||||
start: value.selection.start < 0 ? value.text.length : value.selection.start,
|
||||
end: value.selection.end < 0 ? value.text.length : value.selection.end,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
KeyEventResult _onKey(FocusNode node, KeyEvent event) {
|
||||
if (event is! KeyDownEvent) return KeyEventResult.ignored;
|
||||
final isCmd = HardwareKeyboard.instance.isMetaPressed ||
|
||||
HardwareKeyboard.instance.isControlPressed;
|
||||
final isCmd = HardwareKeyboard.instance.isMetaPressed || HardwareKeyboard.instance.isControlPressed;
|
||||
if (isCmd && event.logicalKey == LogicalKeyboardKey.keyS) {
|
||||
unawaited(_controller?.save());
|
||||
return KeyEventResult.handled;
|
||||
|
||||
@@ -8,8 +8,7 @@ import 'package:clide/kernel/src/theme/tokens.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class SyntaxTextController extends TextEditingController {
|
||||
SyntaxTextController({required TreeSitterService syntax})
|
||||
: _syntax = syntax;
|
||||
SyntaxTextController({required TreeSitterService syntax}) : _syntax = syntax;
|
||||
|
||||
final TreeSitterService _syntax;
|
||||
|
||||
@@ -74,8 +73,7 @@ class SyntaxTextController extends TextEditingController {
|
||||
|
||||
// Convert byte offsets to character offsets.
|
||||
// Build a byte-to-char map only up to the max byte we need.
|
||||
final spans = _spans.where((s) => s.end <= sourceBytes.length).toList()
|
||||
..sort((a, b) => a.start != b.start ? a.start - b.start : a.end - b.end);
|
||||
final spans = _spans.where((s) => s.end <= sourceBytes.length).toList()..sort((a, b) => a.start != b.start ? a.start - b.start : a.end - b.end);
|
||||
|
||||
if (spans.isEmpty) {
|
||||
return TextSpan(text: text, style: style);
|
||||
@@ -121,9 +119,7 @@ class SyntaxTextController extends TextEditingController {
|
||||
continue;
|
||||
}
|
||||
final spanCharStart = byteToChar[span.start];
|
||||
final spanCharEnd = span.end <= maxByte
|
||||
? byteToChar[span.end]
|
||||
: source.length;
|
||||
final spanCharEnd = span.end <= maxByte ? byteToChar[span.end] : source.length;
|
||||
|
||||
if (spanCharStart < charPos) continue;
|
||||
|
||||
|
||||
@@ -136,8 +136,7 @@ class _Children extends StatelessWidget {
|
||||
controller: controller,
|
||||
depth: depth,
|
||||
),
|
||||
if (controller.isExpanded(e.path))
|
||||
_Children(path: e.path, controller: controller, depth: depth + 1),
|
||||
if (controller.isExpanded(e.path)) _Children(path: e.path, controller: controller, depth: depth + 1),
|
||||
],
|
||||
)
|
||||
else
|
||||
@@ -294,4 +293,3 @@ class _FilteredFileRow extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,94 +76,93 @@ class _GitPanelViewState extends State<GitPanelView> {
|
||||
child: Column(
|
||||
children: [
|
||||
ClideFilterBox(hint: 'Filter changes…', onChanged: (v) => setState(() => _filter = v)),
|
||||
Expanded(child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_BranchHeader(controller: c),
|
||||
if (c.error != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 4),
|
||||
child: ClideText(
|
||||
c.error!,
|
||||
color: tokens.statusError,
|
||||
fontSize: clideFontCaption,
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
if (c.loading && c.isClean)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: ClideText('Loading…', muted: true),
|
||||
),
|
||||
if (!c.loading && c.isClean && c.error == null)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: ClideText('Nothing to commit, working tree clean.',
|
||||
muted: true),
|
||||
),
|
||||
if (c.conflicted.isNotEmpty)
|
||||
_FileGroup(
|
||||
label: 'Merge conflicts',
|
||||
entries: _applyFilter(c.conflicted),
|
||||
actions: const [],
|
||||
),
|
||||
if (c.staged.isNotEmpty) ...[
|
||||
_FileGroup(
|
||||
label: 'Staged',
|
||||
entries: _applyFilter(c.staged),
|
||||
actions: [
|
||||
_GroupAction(
|
||||
label: 'Unstage all',
|
||||
onTap: () => unawaited(c.unstage(const [])),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_BranchHeader(controller: c),
|
||||
if (c.error != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: ClideText(
|
||||
c.error!,
|
||||
color: tokens.statusError,
|
||||
fontSize: clideFontCaption,
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
if (c.loading && c.isClean)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: ClideText('Loading…', muted: true),
|
||||
),
|
||||
if (!c.loading && c.isClean && c.error == null)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: ClideText('Nothing to commit, working tree clean.', muted: true),
|
||||
),
|
||||
if (c.conflicted.isNotEmpty)
|
||||
_FileGroup(
|
||||
label: 'Merge conflicts',
|
||||
entries: _applyFilter(c.conflicted),
|
||||
actions: const [],
|
||||
),
|
||||
if (c.staged.isNotEmpty) ...[
|
||||
_FileGroup(
|
||||
label: 'Staged',
|
||||
entries: _applyFilter(c.staged),
|
||||
actions: [
|
||||
_GroupAction(
|
||||
label: 'Unstage all',
|
||||
onTap: () => unawaited(c.unstage(const [])),
|
||||
),
|
||||
],
|
||||
onUnstage: (path) => unawaited(c.unstage([path])),
|
||||
),
|
||||
_CommitInput(
|
||||
commitMsg: _commitMsg,
|
||||
commitFocus: _commitFocus,
|
||||
controller: c,
|
||||
),
|
||||
],
|
||||
onUnstage: (path) => unawaited(c.unstage([path])),
|
||||
),
|
||||
_CommitInput(
|
||||
commitMsg: _commitMsg,
|
||||
commitFocus: _commitFocus,
|
||||
controller: c,
|
||||
),
|
||||
],
|
||||
if (c.unstaged.isNotEmpty)
|
||||
_FileGroup(
|
||||
label: 'Changes',
|
||||
entries: _applyFilter(c.unstaged),
|
||||
actions: [
|
||||
_GroupAction(
|
||||
label: 'Stage all',
|
||||
onTap: () => unawaited(c.stageAll()),
|
||||
if (c.unstaged.isNotEmpty)
|
||||
_FileGroup(
|
||||
label: 'Changes',
|
||||
entries: _applyFilter(c.unstaged),
|
||||
actions: [
|
||||
_GroupAction(
|
||||
label: 'Stage all',
|
||||
onTap: () => unawaited(c.stageAll()),
|
||||
),
|
||||
],
|
||||
onStage: (path) => unawaited(c.stage([path])),
|
||||
onDiscard: (path) => _confirmDiscard(context, c, path),
|
||||
),
|
||||
],
|
||||
onStage: (path) => unawaited(c.stage([path])),
|
||||
onDiscard: (path) => _confirmDiscard(context, c, path),
|
||||
),
|
||||
if (c.untracked.isNotEmpty)
|
||||
_FileGroup(
|
||||
label: 'Untracked',
|
||||
entries: _applyFilter(c.untracked),
|
||||
actions: [
|
||||
_GroupAction(
|
||||
label: 'Stage all',
|
||||
onTap: () {
|
||||
final paths = [
|
||||
for (final e in c.untracked) e['path'] as String,
|
||||
];
|
||||
unawaited(c.stage(paths));
|
||||
},
|
||||
if (c.untracked.isNotEmpty)
|
||||
_FileGroup(
|
||||
label: 'Untracked',
|
||||
entries: _applyFilter(c.untracked),
|
||||
actions: [
|
||||
_GroupAction(
|
||||
label: 'Stage all',
|
||||
onTap: () {
|
||||
final paths = [
|
||||
for (final e in c.untracked) e['path'] as String,
|
||||
];
|
||||
unawaited(c.stage(paths));
|
||||
},
|
||||
),
|
||||
],
|
||||
onStage: (path) => unawaited(c.stage([path])),
|
||||
),
|
||||
],
|
||||
onStage: (path) => unawaited(c.stage([path])),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -369,8 +368,7 @@ class _GitFileRow extends StatelessWidget {
|
||||
},
|
||||
builder: (context, hovered, _) => Container(
|
||||
color: hovered ? tokens.sidebarItemHover : null,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20, right: 8, top: 2, bottom: 2),
|
||||
padding: const EdgeInsets.only(left: 20, right: 8, top: 2, bottom: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
ClideText(
|
||||
|
||||
@@ -140,8 +140,7 @@ class _BranchPickerState extends State<_BranchPicker> {
|
||||
_loading = false;
|
||||
if (r.ok) {
|
||||
_branches = [
|
||||
for (final b in (r.data['branches'] as List? ?? const []))
|
||||
(b as Map).cast<String, Object?>(),
|
||||
for (final b in (r.data['branches'] as List? ?? const [])) (b as Map).cast<String, Object?>(),
|
||||
];
|
||||
} else {
|
||||
_error = r.error?.message ?? 'failed to load branches';
|
||||
@@ -197,10 +196,8 @@ class _BranchPickerState extends State<_BranchPicker> {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_loading)
|
||||
const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)),
|
||||
if (_error != null)
|
||||
Padding(padding: const EdgeInsets.all(12), child: ClideText(_error!, muted: true)),
|
||||
if (_loading) const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)),
|
||||
if (_error != null) Padding(padding: const EdgeInsets.all(12), child: ClideText(_error!, muted: true)),
|
||||
if (!_loading && _error == null && _branches.isEmpty)
|
||||
const Padding(padding: EdgeInsets.all(12), child: ClideText('No branches found.', muted: true)),
|
||||
if (_branches.isNotEmpty)
|
||||
@@ -266,9 +263,7 @@ class _BranchRow extends StatelessWidget {
|
||||
name,
|
||||
fontFamily: clideMonoFamily,
|
||||
fontSize: clideFontMono,
|
||||
color: current
|
||||
? tokens.globalForeground
|
||||
: tokens.listItemForeground,
|
||||
color: current ? tokens.globalForeground : tokens.listItemForeground,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -61,8 +61,7 @@ class _BacklinksViewState extends State<BacklinksView> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: ClideText(
|
||||
c.activePath!.split('/').last,
|
||||
color: tokens.globalForeground,
|
||||
@@ -70,8 +69,7 @@ class _BacklinksViewState extends State<BacklinksView> {
|
||||
),
|
||||
if (c.error != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: ClideText(
|
||||
c.error!,
|
||||
color: tokens.statusError,
|
||||
@@ -120,8 +118,7 @@ class _LinkGroup extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 12, right: 8, top: 8, bottom: 2),
|
||||
padding: const EdgeInsets.only(left: 12, right: 8, top: 8, bottom: 2),
|
||||
child: ClideText(
|
||||
'$label (${links.length})',
|
||||
fontSize: clideFontCaption,
|
||||
@@ -133,8 +130,7 @@ class _LinkGroup extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 2),
|
||||
child: ClideText('None', fontSize: clideFontCaption, muted: true),
|
||||
),
|
||||
for (final link in links)
|
||||
_LinkRow(link: link, pathKey: pathKey),
|
||||
for (final link in links) _LinkRow(link: link, pathKey: pathKey),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -159,21 +155,17 @@ class _LinkRow extends StatelessWidget {
|
||||
onTap: () {
|
||||
if (!target.startsWith('http')) {
|
||||
final kernel = ClideKernel.of(context);
|
||||
unawaited(
|
||||
kernel.ipc.request('editor.open', args: {'path': target}));
|
||||
unawaited(kernel.ipc.request('editor.open', args: {'path': target}));
|
||||
}
|
||||
},
|
||||
builder: (context, hovered, _) => Container(
|
||||
color: hovered ? tokens.sidebarItemHover : null,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 2),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 2),
|
||||
child: ClideText(
|
||||
display,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: target.startsWith('http')
|
||||
? tokens.statusInfo
|
||||
: tokens.sidebarForeground,
|
||||
color: target.startsWith('http') ? tokens.statusInfo : tokens.sidebarForeground,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -43,7 +43,10 @@ class _PqlPanelViewState extends State<PqlPanelView> {
|
||||
if (ctx != null) Scrollable.ensureVisible(ctx, duration: const Duration(milliseconds: 200), alignment: 0.3);
|
||||
});
|
||||
});
|
||||
_fileSub = kernel.events.on<DaemonEvent>().where((e) => e.subsystem == 'files' && e.kind == 'files.changed' && (e.data['path'] as String? ?? '').endsWith('.md')).listen((_) {
|
||||
_fileSub = kernel.events
|
||||
.on<DaemonEvent>()
|
||||
.where((e) => e.subsystem == 'files' && e.kind == 'files.changed' && (e.data['path'] as String? ?? '').endsWith('.md'))
|
||||
.listen((_) {
|
||||
if (_controller?.view == PqlView.markdown) {
|
||||
unawaited(_controller!.loadMarkdownFiles());
|
||||
}
|
||||
@@ -83,8 +86,7 @@ class _PqlPanelViewState extends State<PqlPanelView> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: ClideText(c.error!, color: tokens.statusError, fontSize: clideFontCaption, maxLines: 3),
|
||||
),
|
||||
if (c.loading && c.results.isEmpty)
|
||||
const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)),
|
||||
if (c.loading && c.results.isEmpty) const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)),
|
||||
if (!c.loading && c.results.isEmpty && c.error == null && c.view == PqlView.markdown)
|
||||
const Padding(padding: EdgeInsets.all(12), child: ClideText('No markdown files found.', muted: true)),
|
||||
Expanded(
|
||||
@@ -318,10 +320,7 @@ class _QueryResultRow extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
final name = entry['name'] as String? ?? entry['path'] as String? ?? '';
|
||||
final values = entry.entries
|
||||
.where((e) => e.key != 'name' && e.key != 'path')
|
||||
.map((e) => '${e.key}: ${e.value}')
|
||||
.join(' · ');
|
||||
final values = entry.entries.where((e) => e.key != 'name' && e.key != 'path').map((e) => '${e.key}: ${e.value}').join(' · ');
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
||||
child: Column(
|
||||
|
||||
@@ -55,8 +55,7 @@ class ProblemsController extends ChangeNotifier {
|
||||
}
|
||||
final skill = (doctor.data['skill'] as Map?)?.cast<String, Object?>();
|
||||
if (skill != null) {
|
||||
final project =
|
||||
(skill['project'] as Map?)?.cast<String, Object?>();
|
||||
final project = (skill['project'] as Map?)?.cast<String, Object?>();
|
||||
if (project != null) {
|
||||
final state = project['state'] as String?;
|
||||
if (state == 'stale') {
|
||||
|
||||
@@ -49,7 +49,8 @@ class _ProblemsViewState extends State<ProblemsView> {
|
||||
explicitChildNodes: true,
|
||||
child: () {
|
||||
final lf = _filter.toLowerCase();
|
||||
final filtered = lf.isEmpty ? c.problems : c.problems.where((p) => p.message.toLowerCase().contains(lf) || p.source.toLowerCase().contains(lf)).toList();
|
||||
final filtered =
|
||||
lf.isEmpty ? c.problems : c.problems.where((p) => p.message.toLowerCase().contains(lf) || p.source.toLowerCase().contains(lf)).toList();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
@@ -64,16 +65,15 @@ class _ProblemsViewState extends State<ProblemsView> {
|
||||
label: 'refresh problems',
|
||||
child: GestureDetector(
|
||||
onTap: () => unawaited(c.refresh()),
|
||||
child: MouseRegion(cursor: SystemMouseCursors.click, child: ClideText('Refresh', fontSize: clideFontCaption, color: tokens.sidebarForeground)),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click, child: ClideText('Refresh', fontSize: clideFontCaption, color: tokens.sidebarForeground)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (c.loading && c.problems.isEmpty)
|
||||
const Padding(padding: EdgeInsets.all(12), child: ClideText('Scanning…', muted: true)),
|
||||
if (!c.loading && filtered.isEmpty)
|
||||
const Padding(padding: EdgeInsets.all(12), child: ClideText('No problems found.', muted: true)),
|
||||
if (c.loading && c.problems.isEmpty) const Padding(padding: EdgeInsets.all(12), child: ClideText('Scanning…', muted: true)),
|
||||
if (!c.loading && filtered.isEmpty) const Padding(padding: EdgeInsets.all(12), child: ClideText('No problems found.', muted: true)),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
|
||||
@@ -139,9 +139,7 @@ class _TerminalPaneState extends State<TerminalPane> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subtitle = _error != null
|
||||
? _error!
|
||||
: (_paneId == null ? 'spawning shell…' : 'pid $_pid · ${_paneId!}');
|
||||
final subtitle = _error != null ? _error! : (_paneId == null ? 'spawning shell…' : 'pid $_pid · ${_paneId!}');
|
||||
|
||||
return ClidePaneChrome(
|
||||
title: 'terminal',
|
||||
|
||||
@@ -31,8 +31,7 @@ class _ThemePickerViewState extends State<ThemePickerView> {
|
||||
|
||||
return Semantics(
|
||||
container: true,
|
||||
label: i.string('modal.title',
|
||||
namespace: ThemePickerView.ns, placeholder: 'Select theme'),
|
||||
label: i.string('modal.title', namespace: ThemePickerView.ns, placeholder: 'Select theme'),
|
||||
explicitChildNodes: true,
|
||||
child: ClideSurface(
|
||||
width: 420,
|
||||
@@ -45,8 +44,7 @@ class _ThemePickerViewState extends State<ThemePickerView> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ClideText(
|
||||
i.string('modal.title',
|
||||
namespace: ThemePickerView.ns, placeholder: 'Select theme'),
|
||||
i.string('modal.title', namespace: ThemePickerView.ns, placeholder: 'Select theme'),
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@@ -65,9 +63,7 @@ class _ThemePickerViewState extends State<ThemePickerView> {
|
||||
displayName: t.displayName,
|
||||
selected: t.name == currentName,
|
||||
hovered: _hovered == t.name,
|
||||
hint: i.string('row.select.hint',
|
||||
namespace: ThemePickerView.ns,
|
||||
placeholder: 'Activate this theme'),
|
||||
hint: i.string('row.select.hint', namespace: ThemePickerView.ns, placeholder: 'Activate this theme'),
|
||||
onEnter: () => setState(() => _hovered = t.name),
|
||||
onExit: () => setState(() => _hovered = null),
|
||||
onTap: () {
|
||||
@@ -84,12 +80,9 @@ class _ThemePickerViewState extends State<ThemePickerView> {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ClideButton(
|
||||
label: i.string('modal.cancel',
|
||||
namespace: ThemePickerView.ns, placeholder: 'Cancel'),
|
||||
semanticHint: i.string('modal.cancel.hint',
|
||||
namespace: ThemePickerView.ns,
|
||||
placeholder:
|
||||
'Close the theme picker without changing the current theme'),
|
||||
label: i.string('modal.cancel', namespace: ThemePickerView.ns, placeholder: 'Cancel'),
|
||||
semanticHint:
|
||||
i.string('modal.cancel.hint', namespace: ThemePickerView.ns, placeholder: 'Close the theme picker without changing the current theme'),
|
||||
onPressed: () => widget.onDismiss(),
|
||||
),
|
||||
],
|
||||
@@ -125,14 +118,8 @@ class _ThemeRow extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
final bg = selected
|
||||
? tokens.listItemSelectedBackground
|
||||
: (hovered
|
||||
? tokens.listItemHoverBackground
|
||||
: tokens.listItemBackground);
|
||||
final fg = selected
|
||||
? tokens.listItemSelectedForeground
|
||||
: tokens.listItemForeground;
|
||||
final bg = selected ? tokens.listItemSelectedBackground : (hovered ? tokens.listItemHoverBackground : tokens.listItemBackground);
|
||||
final fg = selected ? tokens.listItemSelectedForeground : tokens.listItemForeground;
|
||||
return Semantics(
|
||||
button: true,
|
||||
selected: selected,
|
||||
|
||||
@@ -40,6 +40,5 @@ class TicketTypeColors {
|
||||
bug: Color(0xFFC03030),
|
||||
);
|
||||
|
||||
static TicketTypeColors forTheme({required bool dark}) =>
|
||||
dark ? TicketTypeColors.dark : TicketTypeColors.light;
|
||||
static TicketTypeColors forTheme({required bool dark}) => dark ? TicketTypeColors.dark : TicketTypeColors.light;
|
||||
}
|
||||
|
||||
@@ -122,8 +122,7 @@ class _TicketHeader extends StatelessWidget {
|
||||
const SizedBox(width: 8),
|
||||
ClideText(detail.id, fontSize: clideFontSmall, color: typeColor, fontFamily: clideMonoFamily),
|
||||
const Spacer(),
|
||||
if (detail.priority != null)
|
||||
ClideText(detail.priority!, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
if (detail.priority != null) ClideText(detail.priority!, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -153,13 +152,18 @@ class _StatusControls extends StatelessWidget {
|
||||
for (final s in _statuses) ...[
|
||||
Expanded(
|
||||
child: ClideTappable(
|
||||
onTap: detail.status == s ? null : () async {
|
||||
final resp = await controller.ipc.request('pql.tickets.status', args: {'ids': [detail.id], 'status': s});
|
||||
if (resp.ok) {
|
||||
controller.messages.publish('builtin.tickets', 'changed', {'id': detail.id});
|
||||
await controller.load(detail.id);
|
||||
}
|
||||
},
|
||||
onTap: detail.status == s
|
||||
? null
|
||||
: () async {
|
||||
final resp = await controller.ipc.request('pql.tickets.status', args: {
|
||||
'ids': [detail.id],
|
||||
'status': s
|
||||
});
|
||||
if (resp.ok) {
|
||||
controller.messages.publish('builtin.tickets', 'changed', {'id': detail.id});
|
||||
await controller.load(detail.id);
|
||||
}
|
||||
},
|
||||
builder: (ctx, hovered, _) {
|
||||
final active = detail.status == s;
|
||||
final color = active ? tokens.statusInfo : (hovered ? tokens.globalForeground : tokens.globalTextMuted);
|
||||
|
||||
@@ -88,7 +88,10 @@ class _TicketsViewState extends State<TicketsView> {
|
||||
|
||||
Future<void> _refresh() async {
|
||||
if (!mounted) return;
|
||||
if (_refreshing) { _pendingRefresh = true; return; }
|
||||
if (_refreshing) {
|
||||
_pendingRefresh = true;
|
||||
return;
|
||||
}
|
||||
_refreshing = true;
|
||||
_pendingRefresh = false;
|
||||
await _load();
|
||||
@@ -130,7 +133,11 @@ class _TicketsViewState extends State<TicketsView> {
|
||||
|
||||
final lf = _filter.toLowerCase();
|
||||
final hasFilter = lf.isNotEmpty;
|
||||
final filtered = hasFilter ? _tickets.where((t) => t.id.toLowerCase().contains(lf) || t.title.toLowerCase().contains(lf) || (t.status ?? '').contains(lf) || (t.type ?? '').contains(lf)).toList() : _tickets;
|
||||
final filtered = hasFilter
|
||||
? _tickets
|
||||
.where((t) => t.id.toLowerCase().contains(lf) || t.title.toLowerCase().contains(lf) || (t.status ?? '').contains(lf) || (t.type ?? '').contains(lf))
|
||||
.toList()
|
||||
: _tickets;
|
||||
|
||||
const sections = [
|
||||
('in_progress', 'IN PROGRESS'),
|
||||
@@ -159,7 +166,8 @@ class _TicketsViewState extends State<TicketsView> {
|
||||
child: ClideTappable(
|
||||
onTap: _refreshing ? null : _refresh,
|
||||
tooltip: 'Refresh tickets',
|
||||
builder: (ctx, hovered, _) => ClideIcon(PhosphorIcons.arrowClockwise, size: 13, color: hovered ? tokens.globalForeground : tokens.globalTextMuted),
|
||||
builder: (ctx, hovered, _) =>
|
||||
ClideIcon(PhosphorIcons.arrowClockwise, size: 13, color: hovered ? tokens.globalForeground : tokens.globalTextMuted),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -115,9 +115,9 @@ class _StartColumn extends StatelessWidget {
|
||||
kernel.panels.activateTab(Slots.workspace, 'claude.primary');
|
||||
} else {
|
||||
kernel.dialog.show((ctx, dismiss) => _NotARepoDialog(
|
||||
path: picked,
|
||||
onDismiss: () => dismiss(),
|
||||
));
|
||||
path: picked,
|
||||
onDismiss: () => dismiss(),
|
||||
));
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -163,8 +163,7 @@ class _ActionRow extends StatelessWidget {
|
||||
ClideIcon(icon, size: 18, color: tokens.globalTextMuted),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(child: ClideText(label, fontSize: 15, color: tokens.globalForeground)),
|
||||
if (shortcut != null)
|
||||
ClideText(shortcut!, fontSize: 13, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
if (shortcut != null) ClideText(shortcut!, fontSize: 13, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -191,8 +190,7 @@ class _RecentColumn extends StatelessWidget {
|
||||
if (recents.isEmpty)
|
||||
const ClideText('No recent projects.', muted: true, fontSize: 14)
|
||||
else
|
||||
for (final r in recents)
|
||||
_RecentRow(project: r, tokens: tokens, onTap: () => _openRecent(r.path)),
|
||||
for (final r in recents) _RecentRow(project: r, tokens: tokens, onTap: () => _openRecent(r.path)),
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -232,7 +230,9 @@ class _RecentRow extends StatelessWidget {
|
||||
const SizedBox(height: 3),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(child: ClideText(project.relativePath, muted: true, fontSize: 13, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis)),
|
||||
Flexible(
|
||||
child: ClideText(project.relativePath,
|
||||
muted: true, fontSize: 13, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis)),
|
||||
if (project.branch != null) ...[
|
||||
ClideText(' · ', muted: true, fontSize: 13),
|
||||
ClideIcon(PhosphorIcons.gitBranch, size: 11, color: tokens.globalTextMuted),
|
||||
@@ -336,7 +336,10 @@ class _OpenProjectDialogState extends State<_OpenProjectDialog> {
|
||||
Future<void> _submit() async {
|
||||
final path = _controller.text.trim();
|
||||
if (path.isEmpty) return;
|
||||
setState(() { _loading = true; _error = null; });
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_error = null;
|
||||
});
|
||||
try {
|
||||
await widget.onOpen(path);
|
||||
} catch (_) {
|
||||
|
||||
+1
-2
@@ -23,8 +23,7 @@ export 'src/files/listing.dart' show FileEntry, listDir;
|
||||
export 'src/git/diff.dart' show GitDiff, GitHunk, DiffLine, DiffLineKind;
|
||||
export 'src/git/client.dart' show GitClient;
|
||||
export 'src/git/operations.dart' show GitLogEntry, GitException;
|
||||
export 'src/git/status.dart'
|
||||
show GitStatus, GitFileStatus, GitFileState, GitConflictType;
|
||||
export 'src/git/status.dart' show GitStatus, GitFileStatus, GitFileState, GitConflictType;
|
||||
export 'src/pql/client.dart' show PqlClient, PqlException;
|
||||
export 'src/ipc/envelope.dart';
|
||||
export 'src/ipc/paths.dart';
|
||||
|
||||
@@ -91,8 +91,7 @@ extension ClideExtensionContextMessages on ClideExtensionContext {
|
||||
extension ClideExtensionContextI18n on ClideExtensionContext {
|
||||
/// `ctx.t('welcome.title', placeholder: 'clide')` →
|
||||
/// `i18n.string('welcome.title', namespace: id, placeholder: 'clide')`.
|
||||
String t(String key, {String? placeholder}) =>
|
||||
i18n.string(key, namespace: id, placeholder: placeholder);
|
||||
String t(String key, {String? placeholder}) => i18n.string(key, namespace: id, placeholder: placeholder);
|
||||
|
||||
/// [t] with interpolation replacers.
|
||||
String tr(
|
||||
|
||||
@@ -55,6 +55,5 @@ class ExtensionManifest {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<ExtensionManifest> fromFile(File f) async =>
|
||||
ExtensionManifest.fromYamlString(await f.readAsString());
|
||||
static Future<ExtensionManifest> fromFile(File f) async => ExtensionManifest.fromYamlString(await f.readAsString());
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import 'package:clide/src/pql/client.dart';
|
||||
class BackendBootMessage {
|
||||
const BackendBootMessage({required this.frontendPort, this.hintRoot});
|
||||
final SendPort frontendPort;
|
||||
|
||||
/// Optional path hint for initial toolchain resolution (e.g. CLIDE_PROJECT).
|
||||
/// Used to find project-local binaries like dugite before a project opens.
|
||||
final String? hintRoot;
|
||||
@@ -61,8 +62,7 @@ void backendEntry(BackendBootMessage boot) {
|
||||
final path = message['path'] as String;
|
||||
final id = message['id'] as String;
|
||||
try {
|
||||
final r = await Process.run(toolchain.git, ['rev-parse', '--show-toplevel'],
|
||||
workingDirectory: path, environment: toolchain.gitEnv);
|
||||
final r = await Process.run(toolchain.git, ['rev-parse', '--show-toplevel'], workingDirectory: path, environment: toolchain.gitEnv);
|
||||
if (r.exitCode == 0) {
|
||||
final root = (r.stdout as String).trim();
|
||||
frontendPort.send({'type': 'project.validated', 'id': id, 'root': root});
|
||||
|
||||
@@ -22,8 +22,7 @@ class ClideClipboard {
|
||||
bucket.insert(0, value);
|
||||
if (bucket.length > historyLimit) bucket.removeLast();
|
||||
if (toPlain != null) {
|
||||
await flutter_services.Clipboard.setData(
|
||||
flutter_services.ClipboardData(text: toPlain(value)));
|
||||
await flutter_services.Clipboard.setData(flutter_services.ClipboardData(text: toPlain(value)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +44,7 @@ class ClideClipboard {
|
||||
}
|
||||
|
||||
Future<void> writePlain(String text) async {
|
||||
await flutter_services.Clipboard.setData(
|
||||
flutter_services.ClipboardData(text: text));
|
||||
await flutter_services.Clipboard.setData(flutter_services.ClipboardData(text: text));
|
||||
final bucket = _history.putIfAbsent(String, () => <Object>[]);
|
||||
bucket.insert(0, text);
|
||||
if (bucket.length > historyLimit) bucket.removeLast();
|
||||
|
||||
@@ -36,10 +36,7 @@ class Keybinding {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is Keybinding &&
|
||||
other.key == key &&
|
||||
listEquals(other.modifiers, modifiers);
|
||||
bool operator ==(Object other) => other is Keybinding && other.key == key && listEquals(other.modifiers, modifiers);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(key, Object.hashAll(modifiers));
|
||||
|
||||
@@ -5,13 +5,11 @@ import 'package:clide/kernel/src/events/types.dart';
|
||||
class DaemonBus {
|
||||
DaemonBus();
|
||||
|
||||
final StreamController<ClideEventEnvelope> _controller =
|
||||
StreamController<ClideEventEnvelope>.broadcast();
|
||||
final StreamController<ClideEventEnvelope> _controller = StreamController<ClideEventEnvelope>.broadcast();
|
||||
|
||||
Stream<ClideEventEnvelope> get stream => _controller.stream;
|
||||
|
||||
Stream<T> on<T extends ClideEvent>() =>
|
||||
_controller.stream.where((e) => e.event is T).map((e) => e.event as T);
|
||||
Stream<T> on<T extends ClideEvent>() => _controller.stream.where((e) => e.event is T).map((e) => e.event as T);
|
||||
|
||||
void emit(ClideEvent event) {
|
||||
if (_controller.isClosed) return;
|
||||
|
||||
@@ -124,8 +124,7 @@ class ExtensionManager extends ChangeNotifier {
|
||||
}
|
||||
for (final dep in ext.dependsOn) {
|
||||
if (!_activated.contains(dep)) {
|
||||
log.warn(
|
||||
'extensions', 'skipping ${ext.id}: dependency not activated: $dep');
|
||||
log.warn('extensions', 'skipping ${ext.id}: dependency not activated: $dep');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -140,8 +139,7 @@ class ExtensionManager extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
log.info('extensions', 'activated $id');
|
||||
} catch (e, st) {
|
||||
log.error('extensions', 'activate failed for $id',
|
||||
error: e, stackTrace: st);
|
||||
log.error('extensions', 'activate failed for $id', error: e, stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +157,7 @@ class ExtensionManager extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
log.info('extensions', 'deactivated $id');
|
||||
} catch (e, st) {
|
||||
log.error('extensions', 'deactivate failed for $id',
|
||||
error: e, stackTrace: st);
|
||||
log.error('extensions', 'deactivate failed for $id', error: e, stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,8 +154,8 @@ class KernelServices {
|
||||
onProjectOpen: onProjectOpen,
|
||||
onValidateProject: onValidateProject,
|
||||
);
|
||||
final ipc = isolateClient
|
||||
?? (daemonClientFactory != null
|
||||
final ipc = isolateClient ??
|
||||
(daemonClientFactory != null
|
||||
? daemonClientFactory(log, events)
|
||||
: DaemonClient(
|
||||
socketPath: socketPath ?? defaultSocketPath(),
|
||||
@@ -257,13 +257,11 @@ class ClideKernel extends InheritedWidget {
|
||||
static KernelServices of(BuildContext context) {
|
||||
final w = context.dependOnInheritedWidgetOfExactType<ClideKernel>();
|
||||
if (w == null) {
|
||||
throw FlutterError(
|
||||
'ClideKernel.of() called with a context that is not a descendant of a ClideKernel.');
|
||||
throw FlutterError('ClideKernel.of() called with a context that is not a descendant of a ClideKernel.');
|
||||
}
|
||||
return w.services;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ClideKernel oldWidget) =>
|
||||
services != oldWidget.services;
|
||||
bool updateShouldNotify(ClideKernel oldWidget) => services != oldWidget.services;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,5 @@ class InMemoryCatalogLoader implements CatalogLoader {
|
||||
return const {};
|
||||
}
|
||||
|
||||
static bool _eq(Locale a, Locale b) =>
|
||||
a.languageCode == b.languageCode && a.countryCode == b.countryCode;
|
||||
static bool _eq(Locale a, Locale b) => a.languageCode == b.languageCode && a.countryCode == b.countryCode;
|
||||
}
|
||||
|
||||
@@ -55,8 +55,7 @@ class I18n extends ChangeNotifier {
|
||||
Locale locale,
|
||||
Map<String, Object?> catalog,
|
||||
) {
|
||||
_cache.putIfAbsent(
|
||||
namespace, () => <Locale, Map<String, Object?>>{})[locale] = catalog;
|
||||
_cache.putIfAbsent(namespace, () => <Locale, Map<String, Object?>>{})[locale] = catalog;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -79,11 +79,7 @@ class DaemonClient extends ChangeNotifier {
|
||||
_backoff = const Duration(milliseconds: 200);
|
||||
_setConnected(true);
|
||||
_log.info('ipc', 'connected to $socketPath');
|
||||
socket
|
||||
.cast<List<int>>()
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen(
|
||||
socket.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter()).listen(
|
||||
_handleLine,
|
||||
onDone: _handleDisconnect,
|
||||
onError: (Object e) {
|
||||
@@ -93,8 +89,7 @@ class DaemonClient extends ChangeNotifier {
|
||||
cancelOnError: true,
|
||||
);
|
||||
} catch (e) {
|
||||
_log.debug(
|
||||
'ipc', 'connect failed ($e); retry in ${_backoff.inMilliseconds}ms');
|
||||
_log.debug('ipc', 'connect failed ($e); retry in ${_backoff.inMilliseconds}ms');
|
||||
_scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
+10
-20
@@ -23,8 +23,7 @@ class LogRecord {
|
||||
@override
|
||||
String toString() {
|
||||
final lv = level.name.toUpperCase().padRight(5);
|
||||
final buf =
|
||||
StringBuffer('${timestamp.toIso8601String()} $lv [$source] $message');
|
||||
final buf = StringBuffer('${timestamp.toIso8601String()} $lv [$source] $message');
|
||||
if (error != null) buf.write(' | error=$error');
|
||||
return buf.toString();
|
||||
}
|
||||
@@ -33,33 +32,24 @@ class LogRecord {
|
||||
typedef LogSink = void Function(LogRecord);
|
||||
|
||||
class Logger {
|
||||
Logger({this.minLevel = LogLevel.info, List<LogSink>? sinks})
|
||||
: _sinks = List<LogSink>.from(sinks ?? <LogSink>[stderrSink]);
|
||||
Logger({this.minLevel = LogLevel.info, List<LogSink>? sinks}) : _sinks = List<LogSink>.from(sinks ?? <LogSink>[stderrSink]);
|
||||
|
||||
LogLevel minLevel;
|
||||
final List<LogSink> _sinks;
|
||||
final StreamController<LogRecord> _stream =
|
||||
StreamController<LogRecord>.broadcast();
|
||||
final StreamController<LogRecord> _stream = StreamController<LogRecord>.broadcast();
|
||||
|
||||
Stream<LogRecord> get records => _stream.stream;
|
||||
|
||||
void addSink(LogSink sink) => _sinks.add(sink);
|
||||
|
||||
void trace(String source, String message) =>
|
||||
_emit(LogLevel.trace, source, message);
|
||||
void debug(String source, String message) =>
|
||||
_emit(LogLevel.debug, source, message);
|
||||
void info(String source, String message) =>
|
||||
_emit(LogLevel.info, source, message);
|
||||
void warn(String source, String message, {Object? error}) =>
|
||||
_emit(LogLevel.warn, source, message, error: error);
|
||||
void error(String source, String message,
|
||||
{Object? error, StackTrace? stackTrace}) =>
|
||||
_emit(LogLevel.error, source, message,
|
||||
error: error, stackTrace: stackTrace);
|
||||
void trace(String source, String message) => _emit(LogLevel.trace, source, message);
|
||||
void debug(String source, String message) => _emit(LogLevel.debug, source, message);
|
||||
void info(String source, String message) => _emit(LogLevel.info, source, message);
|
||||
void warn(String source, String message, {Object? error}) => _emit(LogLevel.warn, source, message, error: error);
|
||||
void error(String source, String message, {Object? error, StackTrace? stackTrace}) =>
|
||||
_emit(LogLevel.error, source, message, error: error, stackTrace: stackTrace);
|
||||
|
||||
void _emit(LogLevel level, String source, String message,
|
||||
{Object? error, StackTrace? stackTrace}) {
|
||||
void _emit(LogLevel level, String source, String message, {Object? error, StackTrace? stackTrace}) {
|
||||
if (level.index < minLevel.index) return;
|
||||
final rec = LogRecord(
|
||||
level: level,
|
||||
|
||||
@@ -29,16 +29,10 @@ class Notifications extends ChangeNotifier {
|
||||
|
||||
List<ClideNotification> get active => List.unmodifiable(_active);
|
||||
|
||||
void info(String message, {String? title, Duration? duration}) =>
|
||||
_push(NotificationLevel.info, message, title: title, duration: duration);
|
||||
void warn(String message, {String? title, Duration? duration}) =>
|
||||
_push(NotificationLevel.warning, message,
|
||||
title: title, duration: duration);
|
||||
void error(String message, {String? title, Duration? duration}) =>
|
||||
_push(NotificationLevel.error, message, title: title, duration: duration);
|
||||
void success(String message, {String? title, Duration? duration}) =>
|
||||
_push(NotificationLevel.success, message,
|
||||
title: title, duration: duration);
|
||||
void info(String message, {String? title, Duration? duration}) => _push(NotificationLevel.info, message, title: title, duration: duration);
|
||||
void warn(String message, {String? title, Duration? duration}) => _push(NotificationLevel.warning, message, title: title, duration: duration);
|
||||
void error(String message, {String? title, Duration? duration}) => _push(NotificationLevel.error, message, title: title, duration: duration);
|
||||
void success(String message, {String? title, Duration? duration}) => _push(NotificationLevel.success, message, title: title, duration: duration);
|
||||
|
||||
void dismiss(String id) {
|
||||
_timers.remove(id)?.cancel();
|
||||
|
||||
@@ -37,9 +37,7 @@ class _DragResizeHandleState extends State<DragResizeHandle> {
|
||||
final lineColor = _hovered ? tokens.panelActiveBorder : tokens.dividerColor;
|
||||
|
||||
return MouseRegion(
|
||||
cursor: widget.axis == Axis.horizontal
|
||||
? SystemMouseCursors.resizeColumn
|
||||
: SystemMouseCursors.resizeRow,
|
||||
cursor: widget.axis == Axis.horizontal ? SystemMouseCursors.resizeColumn : SystemMouseCursors.resizeRow,
|
||||
onEnter: (_) => setState(() => _hovered = true),
|
||||
onExit: (_) => setState(() => _hovered = false),
|
||||
child: Listener(
|
||||
@@ -72,9 +70,7 @@ class _DragResizeHandleState extends State<DragResizeHandle> {
|
||||
final start = _dragStartSize;
|
||||
final startPt = _dragStartPointer;
|
||||
if (start == null || startPt == null) return;
|
||||
final rawDelta = widget.axis == Axis.horizontal
|
||||
? e.position.dx - startPt.dx
|
||||
: e.position.dy - startPt.dy;
|
||||
final rawDelta = widget.axis == Axis.horizontal ? e.position.dx - startPt.dx : e.position.dy - startPt.dy;
|
||||
final delta = widget.slot == Slots.contextPanel ? -rawDelta : rawDelta;
|
||||
widget.arrangement.setSize(widget.slot, start + delta);
|
||||
}
|
||||
|
||||
@@ -53,9 +53,7 @@ class SettingsStore extends ChangeNotifier {
|
||||
return _projectValues[key];
|
||||
case SettingsScope.ext:
|
||||
// project overrides app for the same ext.* key
|
||||
return _projectValues.containsKey(key)
|
||||
? _projectValues[key]
|
||||
: _appValues[key];
|
||||
return _projectValues.containsKey(key) ? _projectValues[key] : _appValues[key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +64,7 @@ class SettingsStore extends ChangeNotifier {
|
||||
await _writeFile(_appFile, _appValues);
|
||||
case SettingsScope.project:
|
||||
if (projectDir == null) {
|
||||
throw StateError(
|
||||
'Cannot set project-scoped key with no project open: $key');
|
||||
throw StateError('Cannot set project-scoped key with no project open: $key');
|
||||
}
|
||||
_projectValues[key] = value;
|
||||
await _writeFile(_projectFile, _projectValues);
|
||||
@@ -109,8 +106,7 @@ class SettingsStore extends ChangeNotifier {
|
||||
if (key.startsWith('app.')) return SettingsScope.app;
|
||||
if (key.startsWith('project.')) return SettingsScope.project;
|
||||
if (key.startsWith('ext.')) return SettingsScope.ext;
|
||||
throw ArgumentError(
|
||||
'Settings key must start with app.|project.|ext.: "$key"');
|
||||
throw ArgumentError('Settings key must start with app.|project.|ext.: "$key"');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,15 @@ import 'package:ffi/ffi.dart';
|
||||
// -- Opaque handles ----------------------------------------------------------
|
||||
|
||||
final class TSParser extends Opaque {}
|
||||
|
||||
final class TSTree extends Opaque {}
|
||||
|
||||
final class TSQuery extends Opaque {}
|
||||
|
||||
final class TSQueryCursor extends Opaque {}
|
||||
|
||||
final class TSWasmStore extends Opaque {}
|
||||
|
||||
final class TSWasmEngine extends Opaque {}
|
||||
|
||||
// -- Structs -----------------------------------------------------------------
|
||||
@@ -51,10 +56,8 @@ final class TSWasmError extends Struct {
|
||||
typedef _TsParserNew = Pointer<TSParser> Function();
|
||||
typedef _TsParserDelete = Void Function(Pointer<TSParser>);
|
||||
typedef _TsParserSetLanguage = Bool Function(Pointer<TSParser>, Pointer<Void>);
|
||||
typedef _TsParserSetWasmStore = Void Function(
|
||||
Pointer<TSParser>, Pointer<TSWasmStore>);
|
||||
typedef _TsParserParseString = Pointer<TSTree> Function(
|
||||
Pointer<TSParser>, Pointer<TSTree>, Pointer<Utf8>, Uint32);
|
||||
typedef _TsParserSetWasmStore = Void Function(Pointer<TSParser>, Pointer<TSWasmStore>);
|
||||
typedef _TsParserParseString = Pointer<TSTree> Function(Pointer<TSParser>, Pointer<TSTree>, Pointer<Utf8>, Uint32);
|
||||
|
||||
// Tree
|
||||
typedef _TsTreeDelete = Void Function(Pointer<TSTree>);
|
||||
@@ -65,28 +68,21 @@ typedef _TsNodeStartByte = Uint32 Function(TSNode);
|
||||
typedef _TsNodeEndByte = Uint32 Function(TSNode);
|
||||
|
||||
// Query
|
||||
typedef _TsQueryNew = Pointer<TSQuery> Function(
|
||||
Pointer<Void>, Pointer<Utf8>, Uint32, Pointer<Uint32>, Pointer<Int32>);
|
||||
typedef _TsQueryNew = Pointer<TSQuery> Function(Pointer<Void>, Pointer<Utf8>, Uint32, Pointer<Uint32>, Pointer<Int32>);
|
||||
typedef _TsQueryDelete = Void Function(Pointer<TSQuery>);
|
||||
typedef _TsQueryCaptureCount = Uint32 Function(Pointer<TSQuery>);
|
||||
typedef _TsQueryCaptureNameForId = Pointer<Utf8> Function(
|
||||
Pointer<TSQuery>, Uint32, Pointer<Uint32>);
|
||||
typedef _TsQueryCaptureNameForId = Pointer<Utf8> Function(Pointer<TSQuery>, Uint32, Pointer<Uint32>);
|
||||
|
||||
// Query cursor
|
||||
typedef _TsQueryCursorNew = Pointer<TSQueryCursor> Function();
|
||||
typedef _TsQueryCursorDelete = Void Function(Pointer<TSQueryCursor>);
|
||||
typedef _TsQueryCursorExec = Void Function(
|
||||
Pointer<TSQueryCursor>, Pointer<TSQuery>, TSNode);
|
||||
typedef _TsQueryCursorNextMatch = Bool Function(
|
||||
Pointer<TSQueryCursor>, Pointer<TSQueryMatch>);
|
||||
typedef _TsQueryCursorExec = Void Function(Pointer<TSQueryCursor>, Pointer<TSQuery>, TSNode);
|
||||
typedef _TsQueryCursorNextMatch = Bool Function(Pointer<TSQueryCursor>, Pointer<TSQueryMatch>);
|
||||
|
||||
// WASM store
|
||||
typedef _TsWasmStoreNew = Pointer<TSWasmStore> Function(
|
||||
Pointer<TSWasmEngine>, Pointer<TSWasmError>);
|
||||
typedef _TsWasmStoreNew = Pointer<TSWasmStore> Function(Pointer<TSWasmEngine>, Pointer<TSWasmError>);
|
||||
typedef _TsWasmStoreDelete = Void Function(Pointer<TSWasmStore>);
|
||||
typedef _TsWasmStoreLoadLanguage = Pointer<Void> Function(
|
||||
Pointer<TSWasmStore>, Pointer<Utf8>, Pointer<Uint8>, Uint32,
|
||||
Pointer<TSWasmError>);
|
||||
typedef _TsWasmStoreLoadLanguage = Pointer<Void> Function(Pointer<TSWasmStore>, Pointer<Utf8>, Pointer<Uint8>, Uint32, Pointer<TSWasmError>);
|
||||
|
||||
// WASM engine (from wasmtime C API, re-exported by tree-sitter)
|
||||
typedef _WasmEngineNew = Pointer<TSWasmEngine> Function();
|
||||
@@ -97,10 +93,8 @@ typedef _WasmEngineDelete = Void Function(Pointer<TSWasmEngine>);
|
||||
typedef DTsParserNew = Pointer<TSParser> Function();
|
||||
typedef DTsParserDelete = void Function(Pointer<TSParser>);
|
||||
typedef DTsParserSetLanguage = bool Function(Pointer<TSParser>, Pointer<Void>);
|
||||
typedef DTsParserSetWasmStore = void Function(
|
||||
Pointer<TSParser>, Pointer<TSWasmStore>);
|
||||
typedef DTsParserParseString = Pointer<TSTree> Function(
|
||||
Pointer<TSParser>, Pointer<TSTree>, Pointer<Utf8>, int);
|
||||
typedef DTsParserSetWasmStore = void Function(Pointer<TSParser>, Pointer<TSWasmStore>);
|
||||
typedef DTsParserParseString = Pointer<TSTree> Function(Pointer<TSParser>, Pointer<TSTree>, Pointer<Utf8>, int);
|
||||
|
||||
typedef DTsTreeDelete = void Function(Pointer<TSTree>);
|
||||
typedef DTsTreeRootNode = TSNode Function(Pointer<TSTree>);
|
||||
@@ -108,26 +102,19 @@ typedef DTsTreeRootNode = TSNode Function(Pointer<TSTree>);
|
||||
typedef DTsNodeStartByte = int Function(TSNode);
|
||||
typedef DTsNodeEndByte = int Function(TSNode);
|
||||
|
||||
typedef DTsQueryNew = Pointer<TSQuery> Function(
|
||||
Pointer<Void>, Pointer<Utf8>, int, Pointer<Uint32>, Pointer<Int32>);
|
||||
typedef DTsQueryNew = Pointer<TSQuery> Function(Pointer<Void>, Pointer<Utf8>, int, Pointer<Uint32>, Pointer<Int32>);
|
||||
typedef DTsQueryDelete = void Function(Pointer<TSQuery>);
|
||||
typedef DTsQueryCaptureCount = int Function(Pointer<TSQuery>);
|
||||
typedef DTsQueryCaptureNameForId = Pointer<Utf8> Function(
|
||||
Pointer<TSQuery>, int, Pointer<Uint32>);
|
||||
typedef DTsQueryCaptureNameForId = Pointer<Utf8> Function(Pointer<TSQuery>, int, Pointer<Uint32>);
|
||||
|
||||
typedef DTsQueryCursorNew = Pointer<TSQueryCursor> Function();
|
||||
typedef DTsQueryCursorDelete = void Function(Pointer<TSQueryCursor>);
|
||||
typedef DTsQueryCursorExec = void Function(
|
||||
Pointer<TSQueryCursor>, Pointer<TSQuery>, TSNode);
|
||||
typedef DTsQueryCursorNextMatch = bool Function(
|
||||
Pointer<TSQueryCursor>, Pointer<TSQueryMatch>);
|
||||
typedef DTsQueryCursorExec = void Function(Pointer<TSQueryCursor>, Pointer<TSQuery>, TSNode);
|
||||
typedef DTsQueryCursorNextMatch = bool Function(Pointer<TSQueryCursor>, Pointer<TSQueryMatch>);
|
||||
|
||||
typedef DTsWasmStoreNew = Pointer<TSWasmStore> Function(
|
||||
Pointer<TSWasmEngine>, Pointer<TSWasmError>);
|
||||
typedef DTsWasmStoreNew = Pointer<TSWasmStore> Function(Pointer<TSWasmEngine>, Pointer<TSWasmError>);
|
||||
typedef DTsWasmStoreDelete = void Function(Pointer<TSWasmStore>);
|
||||
typedef DTsWasmStoreLoadLanguage = Pointer<Void> Function(
|
||||
Pointer<TSWasmStore>, Pointer<Utf8>, Pointer<Uint8>, int,
|
||||
Pointer<TSWasmError>);
|
||||
typedef DTsWasmStoreLoadLanguage = Pointer<Void> Function(Pointer<TSWasmStore>, Pointer<Utf8>, Pointer<Uint8>, int, Pointer<TSWasmError>);
|
||||
|
||||
typedef DWasmEngineNew = Pointer<TSWasmEngine> Function();
|
||||
typedef DWasmEngineDelete = void Function(Pointer<TSWasmEngine>);
|
||||
@@ -136,60 +123,28 @@ typedef DWasmEngineDelete = void Function(Pointer<TSWasmEngine>);
|
||||
|
||||
class TreeSitterLib {
|
||||
TreeSitterLib._(DynamicLibrary lib)
|
||||
: parserNew = lib.lookupFunction<_TsParserNew, DTsParserNew>(
|
||||
'ts_parser_new'),
|
||||
parserDelete = lib.lookupFunction<_TsParserDelete, DTsParserDelete>(
|
||||
'ts_parser_delete'),
|
||||
parserSetLanguage =
|
||||
lib.lookupFunction<_TsParserSetLanguage, DTsParserSetLanguage>(
|
||||
'ts_parser_set_language'),
|
||||
parserSetWasmStore =
|
||||
lib.lookupFunction<_TsParserSetWasmStore, DTsParserSetWasmStore>(
|
||||
'ts_parser_set_wasm_store'),
|
||||
parserParseString =
|
||||
lib.lookupFunction<_TsParserParseString, DTsParserParseString>(
|
||||
'ts_parser_parse_string'),
|
||||
treeDelete = lib.lookupFunction<_TsTreeDelete, DTsTreeDelete>(
|
||||
'ts_tree_delete'),
|
||||
treeRootNode = lib.lookupFunction<_TsTreeRootNode, DTsTreeRootNode>(
|
||||
'ts_tree_root_node'),
|
||||
nodeStartByte = lib.lookupFunction<_TsNodeStartByte, DTsNodeStartByte>(
|
||||
'ts_node_start_byte'),
|
||||
nodeEndByte = lib.lookupFunction<_TsNodeEndByte, DTsNodeEndByte>(
|
||||
'ts_node_end_byte'),
|
||||
queryNew =
|
||||
lib.lookupFunction<_TsQueryNew, DTsQueryNew>('ts_query_new'),
|
||||
queryDelete = lib.lookupFunction<_TsQueryDelete, DTsQueryDelete>(
|
||||
'ts_query_delete'),
|
||||
queryCaptureCount =
|
||||
lib.lookupFunction<_TsQueryCaptureCount, DTsQueryCaptureCount>(
|
||||
'ts_query_capture_count'),
|
||||
queryCaptureNameForId = lib.lookupFunction<_TsQueryCaptureNameForId,
|
||||
DTsQueryCaptureNameForId>('ts_query_capture_name_for_id'),
|
||||
queryCursorNew =
|
||||
lib.lookupFunction<_TsQueryCursorNew, DTsQueryCursorNew>(
|
||||
'ts_query_cursor_new'),
|
||||
queryCursorDelete =
|
||||
lib.lookupFunction<_TsQueryCursorDelete, DTsQueryCursorDelete>(
|
||||
'ts_query_cursor_delete'),
|
||||
queryCursorExec =
|
||||
lib.lookupFunction<_TsQueryCursorExec, DTsQueryCursorExec>(
|
||||
'ts_query_cursor_exec'),
|
||||
queryCursorNextMatch =
|
||||
lib.lookupFunction<_TsQueryCursorNextMatch, DTsQueryCursorNextMatch>(
|
||||
'ts_query_cursor_next_match'),
|
||||
wasmStoreNew = lib.lookupFunction<_TsWasmStoreNew, DTsWasmStoreNew>(
|
||||
'ts_wasm_store_new'),
|
||||
wasmStoreDelete =
|
||||
lib.lookupFunction<_TsWasmStoreDelete, DTsWasmStoreDelete>(
|
||||
'ts_wasm_store_delete'),
|
||||
wasmStoreLoadLanguage = lib.lookupFunction<_TsWasmStoreLoadLanguage,
|
||||
DTsWasmStoreLoadLanguage>('ts_wasm_store_load_language'),
|
||||
wasmEngineNew = lib.lookupFunction<_WasmEngineNew, DWasmEngineNew>(
|
||||
'wasm_engine_new'),
|
||||
wasmEngineDelete =
|
||||
lib.lookupFunction<_WasmEngineDelete, DWasmEngineDelete>(
|
||||
'wasm_engine_delete');
|
||||
: parserNew = lib.lookupFunction<_TsParserNew, DTsParserNew>('ts_parser_new'),
|
||||
parserDelete = lib.lookupFunction<_TsParserDelete, DTsParserDelete>('ts_parser_delete'),
|
||||
parserSetLanguage = lib.lookupFunction<_TsParserSetLanguage, DTsParserSetLanguage>('ts_parser_set_language'),
|
||||
parserSetWasmStore = lib.lookupFunction<_TsParserSetWasmStore, DTsParserSetWasmStore>('ts_parser_set_wasm_store'),
|
||||
parserParseString = lib.lookupFunction<_TsParserParseString, DTsParserParseString>('ts_parser_parse_string'),
|
||||
treeDelete = lib.lookupFunction<_TsTreeDelete, DTsTreeDelete>('ts_tree_delete'),
|
||||
treeRootNode = lib.lookupFunction<_TsTreeRootNode, DTsTreeRootNode>('ts_tree_root_node'),
|
||||
nodeStartByte = lib.lookupFunction<_TsNodeStartByte, DTsNodeStartByte>('ts_node_start_byte'),
|
||||
nodeEndByte = lib.lookupFunction<_TsNodeEndByte, DTsNodeEndByte>('ts_node_end_byte'),
|
||||
queryNew = lib.lookupFunction<_TsQueryNew, DTsQueryNew>('ts_query_new'),
|
||||
queryDelete = lib.lookupFunction<_TsQueryDelete, DTsQueryDelete>('ts_query_delete'),
|
||||
queryCaptureCount = lib.lookupFunction<_TsQueryCaptureCount, DTsQueryCaptureCount>('ts_query_capture_count'),
|
||||
queryCaptureNameForId = lib.lookupFunction<_TsQueryCaptureNameForId, DTsQueryCaptureNameForId>('ts_query_capture_name_for_id'),
|
||||
queryCursorNew = lib.lookupFunction<_TsQueryCursorNew, DTsQueryCursorNew>('ts_query_cursor_new'),
|
||||
queryCursorDelete = lib.lookupFunction<_TsQueryCursorDelete, DTsQueryCursorDelete>('ts_query_cursor_delete'),
|
||||
queryCursorExec = lib.lookupFunction<_TsQueryCursorExec, DTsQueryCursorExec>('ts_query_cursor_exec'),
|
||||
queryCursorNextMatch = lib.lookupFunction<_TsQueryCursorNextMatch, DTsQueryCursorNextMatch>('ts_query_cursor_next_match'),
|
||||
wasmStoreNew = lib.lookupFunction<_TsWasmStoreNew, DTsWasmStoreNew>('ts_wasm_store_new'),
|
||||
wasmStoreDelete = lib.lookupFunction<_TsWasmStoreDelete, DTsWasmStoreDelete>('ts_wasm_store_delete'),
|
||||
wasmStoreLoadLanguage = lib.lookupFunction<_TsWasmStoreLoadLanguage, DTsWasmStoreLoadLanguage>('ts_wasm_store_load_language'),
|
||||
wasmEngineNew = lib.lookupFunction<_WasmEngineNew, DWasmEngineNew>('wasm_engine_new'),
|
||||
wasmEngineDelete = lib.lookupFunction<_WasmEngineDelete, DWasmEngineDelete>('wasm_engine_delete');
|
||||
|
||||
final DTsParserNew parserNew;
|
||||
final DTsParserDelete parserDelete;
|
||||
|
||||
@@ -96,8 +96,7 @@ class TreeSitterService {
|
||||
|
||||
try {
|
||||
// Load grammar WASM bytes.
|
||||
final wasmData =
|
||||
await rootBundle.load('assets/grammars/$language.wasm');
|
||||
final wasmData = await rootBundle.load('assets/grammars/$language.wasm');
|
||||
final wasmBytes = wasmData.buffer.asUint8List();
|
||||
|
||||
// Load into WASM store.
|
||||
@@ -107,7 +106,11 @@ class TreeSitterService {
|
||||
final error = calloc<TSWasmError>();
|
||||
|
||||
final lang = lib.wasmStoreLoadLanguage(
|
||||
_store!, nameNative.cast(), wasmNative, wasmBytes.length, error,
|
||||
_store!,
|
||||
nameNative.cast(),
|
||||
wasmNative,
|
||||
wasmBytes.length,
|
||||
error,
|
||||
);
|
||||
|
||||
calloc.free(wasmNative);
|
||||
@@ -125,8 +128,7 @@ class TreeSitterService {
|
||||
// Load highlight query.
|
||||
String? querySource;
|
||||
try {
|
||||
querySource =
|
||||
await rootBundle.loadString('assets/queries/$language.scm');
|
||||
querySource = await rootBundle.loadString('assets/queries/$language.scm');
|
||||
} catch (_) {}
|
||||
|
||||
Pointer<TSQuery> query = nullptr;
|
||||
@@ -139,7 +141,11 @@ class TreeSitterService {
|
||||
final errorType = calloc<Int32>();
|
||||
|
||||
query = lib.queryNew(
|
||||
lang, queryNative.cast(), queryLen, errorOffset, errorType,
|
||||
lang,
|
||||
queryNative.cast(),
|
||||
queryLen,
|
||||
errorOffset,
|
||||
errorType,
|
||||
);
|
||||
|
||||
calloc.free(queryNative);
|
||||
@@ -205,7 +211,10 @@ class TreeSitterService {
|
||||
final sourceNative = source.toNativeUtf8();
|
||||
final sourceLen = utf8.encode(source).length;
|
||||
final tree = lib.parserParseString(
|
||||
parser, nullptr, sourceNative.cast(), sourceLen,
|
||||
parser,
|
||||
nullptr,
|
||||
sourceNative.cast(),
|
||||
sourceLen,
|
||||
);
|
||||
|
||||
if (tree == nullptr) {
|
||||
@@ -266,21 +275,14 @@ class TreeSitterService {
|
||||
|
||||
static Color colorForRole(String role, SurfaceTokens tokens) {
|
||||
return switch (role) {
|
||||
'keyword' || 'repeat' || 'conditional' || 'include' ||
|
||||
'exception' || 'operator' =>
|
||||
tokens.syntaxKeyword,
|
||||
'keyword' || 'repeat' || 'conditional' || 'include' || 'exception' || 'operator' => tokens.syntaxKeyword,
|
||||
'type' || 'type.builtin' || 'constructor' => tokens.syntaxType,
|
||||
'string' || 'string.special' => tokens.syntaxString,
|
||||
'number' || 'float' || 'boolean' => tokens.syntaxNumber,
|
||||
'comment' => tokens.syntaxComment,
|
||||
'function' || 'function.builtin' || 'function.method' ||
|
||||
'method' =>
|
||||
tokens.syntaxMethod,
|
||||
'punctuation.bracket' || 'punctuation.delimiter' ||
|
||||
'punctuation.special' =>
|
||||
tokens.syntaxPunct,
|
||||
'variable' || 'variable.builtin' || 'variable.parameter' =>
|
||||
tokens.globalForeground,
|
||||
'function' || 'function.builtin' || 'function.method' || 'method' => tokens.syntaxMethod,
|
||||
'punctuation.bracket' || 'punctuation.delimiter' || 'punctuation.special' => tokens.syntaxPunct,
|
||||
'variable' || 'variable.builtin' || 'variable.parameter' => tokens.globalForeground,
|
||||
'property' || 'field' => tokens.syntaxMethod,
|
||||
'constant' || 'constant.builtin' => tokens.syntaxNumber,
|
||||
'tag' || 'attribute' => tokens.syntaxKeyword,
|
||||
|
||||
@@ -145,7 +145,6 @@ Color _composite(Color src, Color dst) {
|
||||
}
|
||||
|
||||
double _relativeLuminance(Color c) {
|
||||
double chan(double v) =>
|
||||
v <= 0.03928 ? v / 12.92 : math.pow((v + 0.055) / 1.055, 2.4).toDouble();
|
||||
double chan(double v) => v <= 0.03928 ? v / 12.92 : math.pow((v + 0.055) / 1.055, 2.4).toDouble();
|
||||
return 0.2126 * chan(c.r) + 0.7152 * chan(c.g) + 0.0722 * chan(c.b);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,7 @@ class ThemeController extends ChangeNotifier {
|
||||
String? initialName,
|
||||
}) : _resolver = resolver,
|
||||
_defs = Map.fromEntries(bundled.map((d) => MapEntry(d.name, d))) {
|
||||
final first = initialName != null && _defs.containsKey(initialName)
|
||||
? initialName
|
||||
: bundled.first.name;
|
||||
final first = initialName != null && _defs.containsKey(initialName) ? initialName : bundled.first.name;
|
||||
_currentName = first;
|
||||
_current = _build(first);
|
||||
}
|
||||
@@ -89,8 +87,7 @@ class ClideTheme extends InheritedNotifier<ThemeController> {
|
||||
static ClideThemeData of(BuildContext context) {
|
||||
final w = context.dependOnInheritedWidgetOfExactType<ClideTheme>();
|
||||
if (w == null) {
|
||||
throw FlutterError(
|
||||
'ClideTheme.of() called with a context that is not a descendant of a ClideTheme.');
|
||||
throw FlutterError('ClideTheme.of() called with a context that is not a descendant of a ClideTheme.');
|
||||
}
|
||||
return w.notifier!.current;
|
||||
}
|
||||
@@ -98,8 +95,7 @@ class ClideTheme extends InheritedNotifier<ThemeController> {
|
||||
static ThemeController controllerOf(BuildContext context) {
|
||||
final w = context.dependOnInheritedWidgetOfExactType<ClideTheme>();
|
||||
if (w == null) {
|
||||
throw FlutterError(
|
||||
'ClideTheme.controllerOf() called with a context that is not a descendant of a ClideTheme.');
|
||||
throw FlutterError('ClideTheme.controllerOf() called with a context that is not a descendant of a ClideTheme.');
|
||||
}
|
||||
return w.notifier!;
|
||||
}
|
||||
|
||||
@@ -80,15 +80,13 @@ class ThemeLoader {
|
||||
displayName: displayName,
|
||||
dark: dark,
|
||||
palette: palette,
|
||||
semanticOverride:
|
||||
semantic is Map ? _parseSemantic(semantic, palette) : null,
|
||||
semanticOverride: semantic is Map ? _parseSemantic(semantic, palette) : null,
|
||||
surfaceOverride: mergedSurface.isNotEmpty ? mergedSurface : null,
|
||||
extensionOverride: extension is Map ? _parseRefMap(extension) : null,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ThemeDefinition> fromAsset(
|
||||
AssetBundle bundle, String assetPath) async {
|
||||
Future<ThemeDefinition> fromAsset(AssetBundle bundle, String assetPath) async {
|
||||
final txt = await bundle.loadString(assetPath);
|
||||
final fallback = assetPath.split('/').last.replaceAll('.yaml', '');
|
||||
return fromYamlString(txt, fallbackName: fallback);
|
||||
@@ -115,8 +113,7 @@ SemanticRoles _parseSemantic(Map src, Palette palette) {
|
||||
final roles = <String, Color>{};
|
||||
src.forEach((k, v) {
|
||||
if (v is! String) return;
|
||||
final resolved =
|
||||
v.startsWith('#') ? Palette.parseHex(v) : palette.lookup(v);
|
||||
final resolved = v.startsWith('#') ? Palette.parseHex(v) : palette.lookup(v);
|
||||
if (resolved != null) roles['$k'] = resolved;
|
||||
});
|
||||
return SemanticRoles(roles);
|
||||
|
||||
@@ -65,10 +65,8 @@ class ThemeResolver {
|
||||
sidebarSectionHeader: surface[TokenKeys.sidebarSectionHeader]!,
|
||||
statusBarBackground: surface[TokenKeys.statusBarBackground]!,
|
||||
statusBarForeground: surface[TokenKeys.statusBarForeground]!,
|
||||
statusBarItemActiveBackground:
|
||||
surface[TokenKeys.statusBarItemActiveBackground]!,
|
||||
statusBarItemHoverBackground:
|
||||
surface[TokenKeys.statusBarItemHoverBackground]!,
|
||||
statusBarItemActiveBackground: surface[TokenKeys.statusBarItemActiveBackground]!,
|
||||
statusBarItemHoverBackground: surface[TokenKeys.statusBarItemHoverBackground]!,
|
||||
tabBarBackground: surface[TokenKeys.tabBarBackground]!,
|
||||
tabActive: surface[TokenKeys.tabActive]!,
|
||||
tabInactive: surface[TokenKeys.tabInactive]!,
|
||||
@@ -84,10 +82,8 @@ class ThemeResolver {
|
||||
listItemBackground: surface[TokenKeys.listItemBackground]!,
|
||||
listItemForeground: surface[TokenKeys.listItemForeground]!,
|
||||
listItemHoverBackground: surface[TokenKeys.listItemHoverBackground]!,
|
||||
listItemSelectedBackground:
|
||||
surface[TokenKeys.listItemSelectedBackground]!,
|
||||
listItemSelectedForeground:
|
||||
surface[TokenKeys.listItemSelectedForeground]!,
|
||||
listItemSelectedBackground: surface[TokenKeys.listItemSelectedBackground]!,
|
||||
listItemSelectedForeground: surface[TokenKeys.listItemSelectedForeground]!,
|
||||
scrollbarSlider: surface[TokenKeys.scrollbarSlider]!,
|
||||
scrollbarSliderHover: surface[TokenKeys.scrollbarSliderHover]!,
|
||||
scrollbarTrack: surface[TokenKeys.scrollbarTrack]!,
|
||||
@@ -135,9 +131,7 @@ class ThemeResolver {
|
||||
// theme never has a null surface color. Themes that omit these
|
||||
// will land readable if uninspired.
|
||||
roles.putIfAbsent(role, () {
|
||||
return palette.lookup('foreground') ??
|
||||
palette.lookup('background') ??
|
||||
const Color(0xFFFFFFFF);
|
||||
return palette.lookup('foreground') ?? palette.lookup('background') ?? const Color(0xFFFFFFFF);
|
||||
});
|
||||
}
|
||||
return SemanticRoles(roles);
|
||||
|
||||
@@ -67,6 +67,7 @@ class Toolchain extends ChangeNotifier {
|
||||
if (!c.isCompleted) c.complete();
|
||||
}
|
||||
}
|
||||
|
||||
addListener(listener);
|
||||
return c.future;
|
||||
}
|
||||
@@ -104,17 +105,16 @@ class Toolchain extends ChangeNotifier {
|
||||
|
||||
final pql = _findOnPath('pql');
|
||||
final tmux = _findOnPath('tmux');
|
||||
final shell = _findOnPath(
|
||||
Platform.environment['SHELL']?.split('/').last ?? 'bash');
|
||||
final shell = _findOnPath(Platform.environment['SHELL']?.split('/').last ?? 'bash');
|
||||
|
||||
final ptyc = _firstExisting([
|
||||
'$workspaceRoot/ptyc/bin/ptyc',
|
||||
'$workspaceRoot/native/linux-x64/ptyc',
|
||||
'$workspaceRoot/native/macos-arm64/ptyc',
|
||||
'$workspaceRoot/native/macos-x64/ptyc',
|
||||
if (Platform.environment['HOME'] case final home?)
|
||||
'$home/.local/bin/ptyc',
|
||||
]) ?? _findOnPath('ptyc');
|
||||
'$workspaceRoot/ptyc/bin/ptyc',
|
||||
'$workspaceRoot/native/linux-x64/ptyc',
|
||||
'$workspaceRoot/native/macos-arm64/ptyc',
|
||||
'$workspaceRoot/native/macos-x64/ptyc',
|
||||
if (Platform.environment['HOME'] case final home?) '$home/.local/bin/ptyc',
|
||||
]) ??
|
||||
_findOnPath('ptyc');
|
||||
|
||||
return ResolvedPaths(
|
||||
git: git,
|
||||
@@ -184,15 +184,14 @@ ResolvedPaths resolveToolchainPaths(String workspaceRoot) {
|
||||
pql: _findOnPathStandalone('pql'),
|
||||
tmux: _findOnPathStandalone('tmux'),
|
||||
ptyc: _firstExistingStandalone([
|
||||
'$workspaceRoot/ptyc/bin/ptyc',
|
||||
'$workspaceRoot/native/linux-x64/ptyc',
|
||||
'$workspaceRoot/native/macos-arm64/ptyc',
|
||||
'$workspaceRoot/native/macos-x64/ptyc',
|
||||
if (Platform.environment['HOME'] case final home?)
|
||||
'$home/.local/bin/ptyc',
|
||||
]) ?? _findOnPathStandalone('ptyc'),
|
||||
shell: _findOnPathStandalone(
|
||||
Platform.environment['SHELL']?.split('/').last ?? 'bash'),
|
||||
'$workspaceRoot/ptyc/bin/ptyc',
|
||||
'$workspaceRoot/native/linux-x64/ptyc',
|
||||
'$workspaceRoot/native/macos-arm64/ptyc',
|
||||
'$workspaceRoot/native/macos-x64/ptyc',
|
||||
if (Platform.environment['HOME'] case final home?) '$home/.local/bin/ptyc',
|
||||
]) ??
|
||||
_findOnPathStandalone('ptyc'),
|
||||
shell: _findOnPathStandalone(Platform.environment['SHELL']?.split('/').last ?? 'bash'),
|
||||
gitEnv: gitEnv,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ class TrayRegistry extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Iterable<TrayItemContribution> get items {
|
||||
final sorted = _items.values.toList()
|
||||
..sort((a, b) => a.priority.compareTo(b.priority));
|
||||
final sorted = _items.values.toList()..sort((a, b) => a.priority.compareTo(b.priority));
|
||||
return sorted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ class LuaHost {
|
||||
|
||||
/// Boot the vendored liblua. Throws until Tier 6.
|
||||
static Future<LuaHost> start() async {
|
||||
throw UnsupportedError(
|
||||
'Lua runtime lands at Tier 6 (supporter tool sibling of ptyc).');
|
||||
throw UnsupportedError('Lua runtime lands at Tier 6 (supporter tool sibling of ptyc).');
|
||||
}
|
||||
|
||||
Future<void> dispose() async {}
|
||||
|
||||
@@ -95,7 +95,9 @@ Future<IpcResponse> _activate(IpcRequest req, EditorRegistry r) async {
|
||||
Future<IpcResponse> _list(IpcRequest req, EditorRegistry r) async {
|
||||
return IpcResponse.ok(
|
||||
id: req.id,
|
||||
data: {'buffers': [for (final b in r.buffers) b.toJson()]},
|
||||
data: {
|
||||
'buffers': [for (final b in r.buffers) b.toJson()]
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -139,9 +141,7 @@ Future<IpcResponse> _setContent(IpcRequest req, EditorRegistry r) async {
|
||||
if (id == null) return _notFound(req.id, 'no active buffer');
|
||||
if (r.get(id) == null) return _notFound(req.id, 'no such buffer: $id');
|
||||
final content = EditorRegistry.contentFromArgs(req.args);
|
||||
final sel = req.args['selection'] == null
|
||||
? null
|
||||
: EditorRegistry.selectionFromArgs(req.args['selection']);
|
||||
final sel = req.args['selection'] == null ? null : EditorRegistry.selectionFromArgs(req.args['selection']);
|
||||
r.setContent(id, content, selection: sel);
|
||||
return IpcResponse.ok(id: req.id, data: {'id': id, 'length': content.length});
|
||||
}
|
||||
@@ -161,4 +161,3 @@ Future<IpcResponse> _close(IpcRequest req, EditorRegistry r) async {
|
||||
r.close(id);
|
||||
return IpcResponse.ok(id: req.id, data: {'id': id});
|
||||
}
|
||||
|
||||
|
||||
@@ -56,13 +56,15 @@ class FilesService {
|
||||
}
|
||||
|
||||
void registerFilesCommands(DaemonDispatcher d, FilesService files) {
|
||||
d.register('files.root', (req) async => IpcResponse.ok(
|
||||
id: req.id,
|
||||
data: {
|
||||
'path': files.root.absolute.path,
|
||||
'ignorePatterns': files.ignore.length,
|
||||
},
|
||||
));
|
||||
d.register(
|
||||
'files.root',
|
||||
(req) async => IpcResponse.ok(
|
||||
id: req.id,
|
||||
data: {
|
||||
'path': files.root.absolute.path,
|
||||
'ignorePatterns': files.ignore.length,
|
||||
},
|
||||
));
|
||||
|
||||
d.register('files.read', (req) async {
|
||||
final path = req.args['path'] as String?;
|
||||
|
||||
@@ -227,7 +227,9 @@ void registerGitCommands(
|
||||
try {
|
||||
final b = await git.branches();
|
||||
return IpcResponse.ok(id: req.id, data: {
|
||||
'branches': [for (final e in b) {'name': e.name, 'current': e.current}],
|
||||
'branches': [
|
||||
for (final e in b) {'name': e.name, 'current': e.current}
|
||||
],
|
||||
});
|
||||
} on GitException catch (e) {
|
||||
return _gitError(req.id, e);
|
||||
|
||||
@@ -27,8 +27,7 @@ void registerPaneCommands(DaemonDispatcher d, PaneRegistry registry) {
|
||||
d.register('pane.tail', (req) => _tail(req, registry));
|
||||
}
|
||||
|
||||
IpcResponse _userErr(String id, String message, {String? hint}) =>
|
||||
IpcResponse.err(
|
||||
IpcResponse _userErr(String id, String message, {String? hint}) => IpcResponse.err(
|
||||
id: id,
|
||||
error: IpcError(
|
||||
code: IpcExitCode.userError,
|
||||
@@ -70,8 +69,7 @@ Future<IpcResponse> _spawn(IpcRequest req, PaneRegistry registry) async {
|
||||
Map<String, String>? env;
|
||||
if (envArg is Map) {
|
||||
env = {
|
||||
for (final e in envArg.entries)
|
||||
'${e.key}': '${e.value}',
|
||||
for (final e in envArg.entries) '${e.key}': '${e.value}',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,7 +99,9 @@ Future<IpcResponse> _spawn(IpcRequest req, PaneRegistry registry) async {
|
||||
Future<IpcResponse> _list(IpcRequest req, PaneRegistry registry) async {
|
||||
return IpcResponse.ok(
|
||||
id: req.id,
|
||||
data: {'panes': [for (final p in registry.panes) p.toJson()]},
|
||||
data: {
|
||||
'panes': [for (final p in registry.panes) p.toJson()]
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,11 @@ void registerPqlCommands(DaemonDispatcher d, PqlClient pql) {
|
||||
|
||||
d.register('pql.tickets.status', (req) async {
|
||||
final rawIds = req.args['ids'];
|
||||
final ids = rawIds is List ? rawIds.cast<String>() : rawIds is String ? [rawIds] : <String>[];
|
||||
final ids = rawIds is List
|
||||
? rawIds.cast<String>()
|
||||
: rawIds is String
|
||||
? [rawIds]
|
||||
: <String>[];
|
||||
final status = req.args['status'] as String?;
|
||||
if (ids.isEmpty || status == null || status.isEmpty) {
|
||||
return _userError(req.id, 'pql.tickets.status requires ids and status');
|
||||
|
||||
@@ -27,8 +27,7 @@ class Selection {
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is Selection && other.start == start && other.end == end;
|
||||
bool operator ==(Object other) => other is Selection && other.start == start && other.end == end;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(start, end);
|
||||
|
||||
@@ -31,8 +31,7 @@ class EditorRegistry {
|
||||
|
||||
Iterable<EditorBuffer> get buffers => _buffers.values;
|
||||
EditorBuffer? get(String id) => _buffers[id];
|
||||
EditorBuffer? get active =>
|
||||
_activeId == null ? null : _buffers[_activeId!];
|
||||
EditorBuffer? get active => _activeId == null ? null : _buffers[_activeId!];
|
||||
|
||||
/// Open a file. If [path] is already open, returns the existing
|
||||
/// buffer (no re-read from disk — the in-memory content is the
|
||||
|
||||
@@ -47,16 +47,12 @@ Future<List<FileEntry>> listDir({
|
||||
required String dir,
|
||||
required IgnoreSet ignore,
|
||||
}) async {
|
||||
final resolved = dir.isEmpty
|
||||
? root
|
||||
: Directory('${root.absolute.path}${Platform.pathSeparator}${dir.replaceAll('/', Platform.pathSeparator)}');
|
||||
final resolved = dir.isEmpty ? root : Directory('${root.absolute.path}${Platform.pathSeparator}${dir.replaceAll('/', Platform.pathSeparator)}');
|
||||
if (!await resolved.exists()) return const [];
|
||||
|
||||
final entries = <FileEntry>[];
|
||||
await for (final e in resolved.list(followLinks: false)) {
|
||||
final name = e.uri.pathSegments.isNotEmpty
|
||||
? e.uri.pathSegments.where((s) => s.isNotEmpty).last
|
||||
: '';
|
||||
final name = e.uri.pathSegments.isNotEmpty ? e.uri.pathSegments.where((s) => s.isNotEmpty).last : '';
|
||||
final rel = dir.isEmpty ? name : '$dir/$name';
|
||||
final stat = await e.stat();
|
||||
final isDir = stat.type == FileSystemEntityType.directory;
|
||||
|
||||
@@ -71,9 +71,9 @@ class FileWatcher {
|
||||
Future<void> start() async {
|
||||
if (_sub != null) return;
|
||||
_sub = root.watch(recursive: true).listen(
|
||||
_onEvent,
|
||||
onError: (Object e, StackTrace _) => _controller.addError(e),
|
||||
);
|
||||
_onEvent,
|
||||
onError: (Object e, StackTrace _) => _controller.addError(e),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
|
||||
@@ -209,9 +209,7 @@ class GitClient {
|
||||
|
||||
Future<ProcessResult> _run(List<String> args) async {
|
||||
try {
|
||||
return await Process.run(toolchain.git, args,
|
||||
workingDirectory: workDir.path,
|
||||
environment: toolchain.gitEnv);
|
||||
return await Process.run(toolchain.git, args, workingDirectory: workDir.path, environment: toolchain.gitEnv);
|
||||
} on ProcessException catch (e) {
|
||||
throw GitException('git ${args.first}: ${e.message}', stderr: e.toString());
|
||||
}
|
||||
@@ -223,9 +221,7 @@ class GitClient {
|
||||
if (reverse) args.add('--reverse');
|
||||
args.addAll(['--unidiff-zero', '-']);
|
||||
|
||||
final proc = await Process.start(toolchain.git, args,
|
||||
workingDirectory: workDir.path,
|
||||
environment: toolchain.gitEnv);
|
||||
final proc = await Process.start(toolchain.git, args, workingDirectory: workDir.path, environment: toolchain.gitEnv);
|
||||
proc.stdin.write(patch);
|
||||
await proc.stdin.close();
|
||||
final exitCode = await proc.exitCode;
|
||||
|
||||
@@ -16,6 +16,7 @@ String get gitBin {
|
||||
_gitBin ??= _resolveGit();
|
||||
return _gitBin!;
|
||||
}
|
||||
|
||||
String? _gitBin;
|
||||
|
||||
String _resolveGit() {
|
||||
@@ -215,8 +216,7 @@ Future<String> gitPush(
|
||||
}
|
||||
|
||||
/// List local branches. Returns (name, isCurrent) pairs.
|
||||
Future<List<({String name, bool current})>> gitBranches(
|
||||
Directory workDir) async {
|
||||
Future<List<({String name, bool current})>> gitBranches(Directory workDir) async {
|
||||
final r = await Process.run(
|
||||
gitBin,
|
||||
['branch', '--format=%(refname:short)|%(HEAD)'],
|
||||
@@ -302,9 +302,7 @@ Future<void> _applyPatch(
|
||||
await proc.stdin.close();
|
||||
final exitCode = await proc.exitCode;
|
||||
if (exitCode != 0) {
|
||||
final stderr = await proc.stderr
|
||||
.transform(const SystemEncoding().decoder)
|
||||
.join();
|
||||
final stderr = await proc.stderr.transform(const SystemEncoding().decoder).join();
|
||||
throw GitException(
|
||||
'git apply failed',
|
||||
stderr: stderr,
|
||||
|
||||
+6
-17
@@ -44,15 +44,8 @@ class GitFileStatus {
|
||||
final String? origPath;
|
||||
final GitConflictType? conflictType;
|
||||
|
||||
bool get isStaged =>
|
||||
indexState != null &&
|
||||
indexState != GitFileState.untracked &&
|
||||
indexState != GitFileState.ignored &&
|
||||
!isConflicted;
|
||||
bool get isUnstaged =>
|
||||
workTreeState != null &&
|
||||
workTreeState != GitFileState.untracked &&
|
||||
!isConflicted;
|
||||
bool get isStaged => indexState != null && indexState != GitFileState.untracked && indexState != GitFileState.ignored && !isConflicted;
|
||||
bool get isUnstaged => workTreeState != null && workTreeState != GitFileState.untracked && !isConflicted;
|
||||
bool get isUntracked => workTreeState == GitFileState.untracked;
|
||||
bool get isConflicted => conflictType != null;
|
||||
|
||||
@@ -84,14 +77,10 @@ class GitStatus {
|
||||
final int behind;
|
||||
final List<GitFileStatus> entries;
|
||||
|
||||
List<GitFileStatus> get staged =>
|
||||
entries.where((e) => e.isStaged).toList();
|
||||
List<GitFileStatus> get unstaged =>
|
||||
entries.where((e) => e.isUnstaged).toList();
|
||||
List<GitFileStatus> get untracked =>
|
||||
entries.where((e) => e.isUntracked).toList();
|
||||
List<GitFileStatus> get conflicted =>
|
||||
entries.where((e) => e.isConflicted).toList();
|
||||
List<GitFileStatus> get staged => entries.where((e) => e.isStaged).toList();
|
||||
List<GitFileStatus> get unstaged => entries.where((e) => e.isUnstaged).toList();
|
||||
List<GitFileStatus> get untracked => entries.where((e) => e.isUntracked).toList();
|
||||
List<GitFileStatus> get conflicted => entries.where((e) => e.isConflicted).toList();
|
||||
|
||||
bool get isClean => entries.isEmpty;
|
||||
bool get hasConflicts => entries.any((e) => e.isConflicted);
|
||||
|
||||
@@ -90,9 +90,7 @@ class IpcResponse extends IpcMessage {
|
||||
id: j['id']! as String,
|
||||
ok: ok,
|
||||
data: (j['data'] as Map?)?.cast<String, Object?>() ?? const {},
|
||||
error: ok
|
||||
? null
|
||||
: IpcError.fromJson((j['error'] as Map).cast<String, Object?>()),
|
||||
error: ok ? null : IpcError.fromJson((j['error'] as Map).cast<String, Object?>()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +69,7 @@ class DaemonServer {
|
||||
|
||||
void _handleClient(Socket client) {
|
||||
_clients.add(client);
|
||||
client
|
||||
.cast<List<int>>()
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen(
|
||||
client.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter()).listen(
|
||||
(line) => _handleLine(client, line),
|
||||
onDone: () => _clients.remove(client),
|
||||
onError: (Object e) {
|
||||
|
||||
@@ -22,10 +22,8 @@ class RecordingEventSink implements DaemonEventSink {
|
||||
void emit(IpcEvent event) => events.add(event);
|
||||
|
||||
/// Convenience: filter to a single subsystem (`pane`, `git`, …).
|
||||
Iterable<IpcEvent> ofSubsystem(String subsystem) =>
|
||||
events.where((e) => e.subsystem == subsystem);
|
||||
Iterable<IpcEvent> ofSubsystem(String subsystem) => events.where((e) => e.subsystem == subsystem);
|
||||
|
||||
/// Convenience: filter to a specific `type` (`pane.spawned`, …).
|
||||
Iterable<IpcEvent> ofKind(String kind) =>
|
||||
events.where((e) => e.kind == kind);
|
||||
Iterable<IpcEvent> ofKind(String kind) => events.where((e) => e.kind == kind);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ class ClideAccordion extends StatelessWidget {
|
||||
ClideIcon(expanded ? PhosphorIcons.caretDown : PhosphorIcons.caretRight, size: 10, color: tokens.globalTextMuted),
|
||||
const SizedBox(width: 6),
|
||||
if (leading != null) ...[leading!, const SizedBox(width: 6)],
|
||||
ClideText('$label · $count', fontSize: clideFontSmall, color: hovered ? tokens.globalForeground : tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
ClideText('$label · $count',
|
||||
fontSize: clideFontSmall, color: hovered ? tokens.globalForeground : tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -52,6 +52,5 @@ class _IconPainterAdapter extends CustomPainter {
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _IconPainterAdapter old) =>
|
||||
old.painter != painter || old.color != color;
|
||||
bool shouldRepaint(covariant _IconPainterAdapter old) => old.painter != painter || old.color != color;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,10 @@ class ClideMarkdown extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [for (final c in el.children ?? const []) if (c is md.Element) _buildListItem(c, tokens, onRecordTap, ordered: false)],
|
||||
children: [
|
||||
for (final c in el.children ?? const [])
|
||||
if (c is md.Element) _buildListItem(c, tokens, onRecordTap, ordered: false)
|
||||
],
|
||||
),
|
||||
);
|
||||
case 'ol':
|
||||
@@ -266,12 +269,18 @@ class ClideMarkdown extends StatelessWidget {
|
||||
case 'strong':
|
||||
return TextSpan(
|
||||
style: const TextStyle(fontWeight: FontWeight.w700),
|
||||
children: [for (final c in el.children ?? const []) if (c is md.Text) TextSpan(text: _unescapeHtml(c.text)) else if (c is md.Element) _inlineElementSpan(c, tokens, onRecordTap)],
|
||||
children: [
|
||||
for (final c in el.children ?? const [])
|
||||
if (c is md.Text) TextSpan(text: _unescapeHtml(c.text)) else if (c is md.Element) _inlineElementSpan(c, tokens, onRecordTap)
|
||||
],
|
||||
);
|
||||
case 'em':
|
||||
return TextSpan(
|
||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||
children: [for (final c in el.children ?? const []) if (c is md.Text) TextSpan(text: _unescapeHtml(c.text)) else if (c is md.Element) _inlineElementSpan(c, tokens, onRecordTap)],
|
||||
children: [
|
||||
for (final c in el.children ?? const [])
|
||||
if (c is md.Text) TextSpan(text: _unescapeHtml(c.text)) else if (c is md.Element) _inlineElementSpan(c, tokens, onRecordTap)
|
||||
],
|
||||
);
|
||||
case 'code':
|
||||
return TextSpan(
|
||||
|
||||
@@ -151,12 +151,13 @@ class _Header extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (trailing != null) ...trailing!.map(
|
||||
(w) => Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: w,
|
||||
if (trailing != null)
|
||||
...trailing!.map(
|
||||
(w) => Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: w,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (onClose != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
|
||||
@@ -47,8 +47,5 @@ class ScrollbarTheme extends InheritedWidget {
|
||||
final Color track;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ScrollbarTheme old) =>
|
||||
slider != old.slider ||
|
||||
sliderHover != old.sliderHover ||
|
||||
track != old.track;
|
||||
bool updateShouldNotify(ScrollbarTheme old) => slider != old.slider || sliderHover != old.sliderHover || track != old.track;
|
||||
}
|
||||
|
||||
@@ -37,8 +37,7 @@ class ClideText extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
final resolved =
|
||||
color ?? (muted ? tokens.globalTextMuted : tokens.globalForeground);
|
||||
final resolved = color ?? (muted ? tokens.globalTextMuted : tokens.globalForeground);
|
||||
return Text(
|
||||
data,
|
||||
maxLines: maxLines,
|
||||
|
||||
@@ -18,8 +18,7 @@ class PlugIcon extends ClideIconPainter {
|
||||
..moveTo(0.30, 0.30)
|
||||
..lineTo(0.60, 0.30)
|
||||
..lineTo(0.60, 0.55)
|
||||
..arcToPoint(const Offset(0.30, 0.55),
|
||||
radius: const Radius.circular(0.15), clockwise: false)
|
||||
..arcToPoint(const Offset(0.30, 0.55), radius: const Radius.circular(0.15), clockwise: false)
|
||||
..close();
|
||||
canvas.drawPath(body, p);
|
||||
// cord
|
||||
|
||||
@@ -33,14 +33,12 @@ void main() {
|
||||
expect(node.hasFocus, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('interactive widgets expose tap actions to a11y',
|
||||
(tester) async {
|
||||
testWidgets('interactive widgets expose tap actions to a11y', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
harness(f, ClideButton(label: 'Save', onPressed: () {})),
|
||||
);
|
||||
final handle = tester.ensureSemantics();
|
||||
final data =
|
||||
tester.getSemantics(find.byType(ClideButton)).getSemanticsData();
|
||||
final data = tester.getSemantics(find.byType(ClideButton)).getSemanticsData();
|
||||
expect(data.hasAction(SemanticsAction.tap), isTrue);
|
||||
handle.dispose();
|
||||
});
|
||||
|
||||
@@ -34,19 +34,15 @@ void main() {
|
||||
test('tab contributions carry title + i18n key + namespace', () {
|
||||
final tabs = ext.contributions.whereType<TabContribution>().toList();
|
||||
for (final t in tabs) {
|
||||
expect(t.title, isNotEmpty,
|
||||
reason: '${ext.id} tab ${t.id} missing English title');
|
||||
expect(t.title, isNotEmpty, reason: '${ext.id} tab ${t.id} missing English title');
|
||||
if (t.titleKey != null) {
|
||||
expect(t.i18nNamespace, isNotNull,
|
||||
reason:
|
||||
'${ext.id} tab ${t.id} has titleKey but no namespace');
|
||||
expect(t.i18nNamespace, isNotNull, reason: '${ext.id} tab ${t.id} has titleKey but no namespace');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('command contributions carry stable ids', () {
|
||||
final cmds =
|
||||
ext.contributions.whereType<CommandContribution>().toList();
|
||||
final cmds = ext.contributions.whereType<CommandContribution>().toList();
|
||||
for (final c in cmds) {
|
||||
expect(c.command, isNotEmpty);
|
||||
expect(c.id, isNotEmpty);
|
||||
|
||||
@@ -20,10 +20,7 @@ void main() {
|
||||
test('contributes a statusbar item', () async {
|
||||
f.services.extensions.register(IpcStatusExtension());
|
||||
await f.services.extensions.activateAll();
|
||||
final items = f.services.panels
|
||||
.contributionsFor(Slots.statusbar)
|
||||
.whereType<StatusItemContribution>()
|
||||
.toList();
|
||||
final items = f.services.panels.contributionsFor(Slots.statusbar).whereType<StatusItemContribution>().toList();
|
||||
expect(items, hasLength(1));
|
||||
expect(items.first.priority, 100);
|
||||
});
|
||||
|
||||
@@ -80,8 +80,7 @@ void main() {
|
||||
expect(find.text('Cancel'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('tapping a row calls controller.select + onDismiss',
|
||||
(tester) async {
|
||||
testWidgets('tapping a row calls controller.select + onDismiss', (tester) async {
|
||||
String? dismissed;
|
||||
await tester.pumpWidget(
|
||||
harness(
|
||||
|
||||
@@ -20,9 +20,7 @@ void main() {
|
||||
'builtin.welcome': {
|
||||
const Locale('en', 'US'): const {
|
||||
'title': {'translation': 'clide'},
|
||||
'subtitle': {
|
||||
'translation': 'Flutter desktop IDE for Claude Code'
|
||||
},
|
||||
'subtitle': {'translation': 'Flutter desktop IDE for Claude Code'},
|
||||
'open-project': {'translation': 'Open project'},
|
||||
'open-project.hint': {'translation': 'Pick a git repository'},
|
||||
'tab.title': {'translation': 'Welcome'},
|
||||
|
||||
@@ -49,10 +49,7 @@ void main() {
|
||||
);
|
||||
// Wait for the "listening" line on stderr so we know it's ready.
|
||||
final ready = Completer<void>();
|
||||
daemon.stderr
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen((line) {
|
||||
daemon.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen((line) {
|
||||
if (!ready.isCompleted && line.contains('listening')) {
|
||||
ready.complete();
|
||||
}
|
||||
@@ -62,8 +59,7 @@ void main() {
|
||||
|
||||
tearDown(() async {
|
||||
daemon.kill(ProcessSignal.sigterm);
|
||||
await daemon.exitCode.timeout(const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
await daemon.exitCode.timeout(const Duration(seconds: 3), onTimeout: () {
|
||||
daemon.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
@@ -129,10 +125,7 @@ void main() {
|
||||
);
|
||||
|
||||
final received = <Map<String, Object?>>[];
|
||||
final sub = tail.stdout
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen((line) {
|
||||
final sub = tail.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((line) {
|
||||
if (line.isEmpty) return;
|
||||
received.add(jsonDecode(line) as Map<String, Object?>);
|
||||
});
|
||||
@@ -149,8 +142,7 @@ void main() {
|
||||
}
|
||||
|
||||
tail.kill(ProcessSignal.sigint);
|
||||
await tail.exitCode.timeout(const Duration(seconds: 2),
|
||||
onTimeout: () {
|
||||
await tail.exitCode.timeout(const Duration(seconds: 2), onTimeout: () {
|
||||
tail.kill(ProcessSignal.sigkill);
|
||||
return -1;
|
||||
});
|
||||
|
||||
@@ -44,8 +44,7 @@ void main() {
|
||||
if (sandbox.existsSync()) sandbox.deleteSync(recursive: true);
|
||||
});
|
||||
|
||||
Future<IpcResponse> call(String cmd,
|
||||
[Map<String, Object?> args = const {}]) {
|
||||
Future<IpcResponse> call(String cmd, [Map<String, Object?> args = const {}]) {
|
||||
return dispatcher.dispatch(IpcRequest(id: '1', cmd: cmd, args: args));
|
||||
}
|
||||
|
||||
@@ -66,7 +65,9 @@ void main() {
|
||||
|
||||
test('git.stage + git.status shows staged file', () async {
|
||||
await File('${sandbox.path}/new.txt').writeAsString('x');
|
||||
final stage = await call('git.stage', {'paths': ['new.txt']});
|
||||
final stage = await call('git.stage', {
|
||||
'paths': ['new.txt']
|
||||
});
|
||||
expect(stage.ok, isTrue);
|
||||
|
||||
final r = await call('git.status');
|
||||
@@ -82,8 +83,12 @@ void main() {
|
||||
|
||||
test('git.unstage removes from staging', () async {
|
||||
await File('${sandbox.path}/new.txt').writeAsString('x');
|
||||
await call('git.stage', {'paths': ['new.txt']});
|
||||
final unstage = await call('git.unstage', {'paths': ['new.txt']});
|
||||
await call('git.stage', {
|
||||
'paths': ['new.txt']
|
||||
});
|
||||
final unstage = await call('git.unstage', {
|
||||
'paths': ['new.txt']
|
||||
});
|
||||
expect(unstage.ok, isTrue);
|
||||
|
||||
final r = await call('git.status');
|
||||
@@ -93,7 +98,9 @@ void main() {
|
||||
|
||||
test('git.commit creates a commit', () async {
|
||||
await File('${sandbox.path}/c.txt').writeAsString('x');
|
||||
await call('git.stage', {'paths': ['c.txt']});
|
||||
await call('git.stage', {
|
||||
'paths': ['c.txt']
|
||||
});
|
||||
final r = await call('git.commit', {'message': 'test commit'});
|
||||
expect(r.ok, isTrue);
|
||||
expect(r.data['hash'], hasLength(40));
|
||||
@@ -115,7 +122,9 @@ void main() {
|
||||
|
||||
test('git.diff --staged returns staged diffs', () async {
|
||||
await File('${sandbox.path}/file.txt').writeAsString('modified\n');
|
||||
await call('git.stage', {'paths': ['file.txt']});
|
||||
await call('git.stage', {
|
||||
'paths': ['file.txt']
|
||||
});
|
||||
final r = await call('git.diff', {'staged': true});
|
||||
expect(r.ok, isTrue);
|
||||
final diffs = r.data['diffs'] as List;
|
||||
@@ -131,7 +140,9 @@ void main() {
|
||||
|
||||
test('git.discard restores a file', () async {
|
||||
await File('${sandbox.path}/file.txt').writeAsString('changed');
|
||||
final r = await call('git.discard', {'paths': ['file.txt']});
|
||||
final r = await call('git.discard', {
|
||||
'paths': ['file.txt']
|
||||
});
|
||||
expect(r.ok, isTrue);
|
||||
final content = await File('${sandbox.path}/file.txt').readAsString();
|
||||
expect(content, 'hello\n');
|
||||
@@ -145,7 +156,9 @@ void main() {
|
||||
|
||||
test('mutations emit git.changed events', () async {
|
||||
await File('${sandbox.path}/e.txt').writeAsString('x');
|
||||
await call('git.stage', {'paths': ['e.txt']});
|
||||
await call('git.stage', {
|
||||
'paths': ['e.txt']
|
||||
});
|
||||
expect(
|
||||
sink.events,
|
||||
contains(predicate<IpcEvent>((e) => e.kind == 'git.changed')),
|
||||
|
||||
@@ -71,19 +71,14 @@ void main() {
|
||||
}
|
||||
final lines = <String>[];
|
||||
final done = Completer<void>();
|
||||
final sub = socket
|
||||
.cast<List<int>>()
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen((line) {
|
||||
final sub = socket.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter()).listen((line) {
|
||||
lines.add(line);
|
||||
if (lines.length == 5) done.complete();
|
||||
});
|
||||
await done.future.timeout(const Duration(seconds: 2));
|
||||
await sub.cancel();
|
||||
await socket.close();
|
||||
final ids =
|
||||
lines.map((l) => (IpcMessage.decode(l) as IpcResponse).id).toSet();
|
||||
final ids = lines.map((l) => (IpcMessage.decode(l) as IpcResponse).id).toSet();
|
||||
expect(ids, {'0', '1', '2', '3', '4'});
|
||||
});
|
||||
|
||||
@@ -112,12 +107,7 @@ Future<String> _send(String socketPath, String line) async {
|
||||
0,
|
||||
);
|
||||
socket.writeln(line);
|
||||
final resp = await socket
|
||||
.cast<List<int>>()
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.first
|
||||
.timeout(const Duration(seconds: 2));
|
||||
final resp = await socket.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter()).first.timeout(const Duration(seconds: 2));
|
||||
await socket.close();
|
||||
return resp;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ void main() {
|
||||
group('bin/clide --daemon (subprocess)', () {
|
||||
setUpAll(() {
|
||||
if (!binary.existsSync()) {
|
||||
markTestSkipped(
|
||||
'bin/clide not built; run `make build` first to enable this suite');
|
||||
markTestSkipped('bin/clide not built; run `make build` first to enable this suite');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -41,10 +40,7 @@ void main() {
|
||||
// Wait for "listening on ..." on stderr before connecting.
|
||||
final ready = Completer<void>();
|
||||
final stderrLines = <String>[];
|
||||
final sub = process.stderr
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen((line) {
|
||||
final sub = process.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen((line) {
|
||||
stderrLines.add(line);
|
||||
if (line.contains('listening')) ready.complete();
|
||||
});
|
||||
@@ -58,12 +54,7 @@ void main() {
|
||||
0,
|
||||
).timeout(const Duration(seconds: 3));
|
||||
sock.writeln(IpcRequest(id: '1', cmd: 'ping').encode());
|
||||
final line = await sock
|
||||
.cast<List<int>>()
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.first
|
||||
.timeout(const Duration(seconds: 3));
|
||||
final line = await sock.cast<List<int>>().transform(utf8.decoder).transform(const LineSplitter()).first.timeout(const Duration(seconds: 3));
|
||||
await sock.close();
|
||||
final resp = IpcMessage.decode(line) as IpcResponse;
|
||||
expect(resp.ok, true);
|
||||
@@ -71,8 +62,7 @@ void main() {
|
||||
|
||||
// Clean shutdown
|
||||
process.kill(ProcessSignal.sigterm);
|
||||
final exitCode =
|
||||
await process.exitCode.timeout(const Duration(seconds: 3));
|
||||
final exitCode = await process.exitCode.timeout(const Duration(seconds: 3));
|
||||
expect(exitCode, 0);
|
||||
|
||||
// Socket file should be unlinked
|
||||
|
||||
@@ -76,8 +76,7 @@ void main() {
|
||||
expect(sink.ofKind('editor.saved'), hasLength(1));
|
||||
});
|
||||
|
||||
test('close picks a new active buffer when the active one closes',
|
||||
() async {
|
||||
test('close picks a new active buffer when the active one closes', () async {
|
||||
final a = await reg.open('README.md');
|
||||
await File('${sandbox.path}/b.txt').writeAsString('two');
|
||||
final b = await reg.open('b.txt');
|
||||
|
||||
@@ -30,11 +30,9 @@ void main() {
|
||||
|
||||
test('discovers extensions from their own subdirs', () async {
|
||||
final a = Directory('${root.path}/ext.a')..createSync();
|
||||
await File('${a.path}/manifest.yaml')
|
||||
.writeAsString('id: ext.a\ntitle: A\nversion: 1.0.0\n');
|
||||
await File('${a.path}/manifest.yaml').writeAsString('id: ext.a\ntitle: A\nversion: 1.0.0\n');
|
||||
final b = Directory('${root.path}/ext.b')..createSync();
|
||||
await File('${b.path}/manifest.yaml')
|
||||
.writeAsString('id: ext.b\ntitle: B\nversion: 1.2.0\n');
|
||||
await File('${b.path}/manifest.yaml').writeAsString('id: ext.b\ntitle: B\nversion: 1.2.0\n');
|
||||
final out = await const ExtensionScanner().discover(root: root);
|
||||
expect(out.map((m) => m.id).toSet(), {'ext.a', 'ext.b'});
|
||||
});
|
||||
|
||||
@@ -185,8 +185,7 @@ index abc..def 100644
|
||||
});
|
||||
|
||||
test('returns unstaged diff after modification', () async {
|
||||
await File('${sandbox.path}/file.txt')
|
||||
.writeAsString('line1\nmodified\n');
|
||||
await File('${sandbox.path}/file.txt').writeAsString('line1\nmodified\n');
|
||||
final diffs = await gitDiff(sandbox);
|
||||
expect(diffs, hasLength(1));
|
||||
expect(diffs.first.path, 'file.txt');
|
||||
@@ -194,8 +193,7 @@ index abc..def 100644
|
||||
});
|
||||
|
||||
test('returns staged diff with staged: true', () async {
|
||||
await File('${sandbox.path}/file.txt')
|
||||
.writeAsString('line1\nmodified\n');
|
||||
await File('${sandbox.path}/file.txt').writeAsString('line1\nmodified\n');
|
||||
await Process.run(
|
||||
'git',
|
||||
['add', 'file.txt'],
|
||||
|
||||
@@ -4,12 +4,10 @@ import 'package:clide/kernel/kernel.dart';
|
||||
/// A DaemonClient that doesn't actually open a socket. Use in tests
|
||||
/// that need a connected-state observable but not a real daemon.
|
||||
class FakeDaemonClient extends DaemonClient {
|
||||
FakeDaemonClient({required super.log, required super.events})
|
||||
: super(socketPath: '/dev/null/fake-clide.sock');
|
||||
FakeDaemonClient({required super.log, required super.events}) : super(socketPath: '/dev/null/fake-clide.sock');
|
||||
|
||||
bool _fakeConnected = false;
|
||||
final Map<String, Future<IpcResponse> Function(Map<String, Object?>)> _stubs =
|
||||
{};
|
||||
final Map<String, Future<IpcResponse> Function(Map<String, Object?>)> _stubs = {};
|
||||
|
||||
@override
|
||||
bool get isConnected => _fakeConnected;
|
||||
|
||||
@@ -9,8 +9,7 @@ import 'fake_ipc.dart';
|
||||
/// No real daemon, no real filesystem outside a temp dir, no asset
|
||||
/// bundle — i18n catalogs are passed as literals.
|
||||
class KernelFixture {
|
||||
KernelFixture._(
|
||||
{required this.services, required this.ipc, required this.tempDir});
|
||||
KernelFixture._({required this.services, required this.ipc, required this.tempDir});
|
||||
|
||||
final KernelServices services;
|
||||
final FakeDaemonClient ipc;
|
||||
|
||||
@@ -117,8 +117,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('throws on malformed JSON', () {
|
||||
expect(() => IpcMessage.decode('{this is not json'),
|
||||
throwsA(isA<FormatException>()));
|
||||
expect(() => IpcMessage.decode('{this is not json'), throwsA(isA<FormatException>()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import 'package:clide/extension/extension.dart';
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
CommandContribution _cmd(String name, Future<IpcResponse> Function() run) =>
|
||||
CommandContribution(
|
||||
CommandContribution _cmd(String name, Future<IpcResponse> Function() run) => CommandContribution(
|
||||
id: name,
|
||||
command: name,
|
||||
title: 'cmd $name',
|
||||
|
||||
@@ -172,12 +172,8 @@ void main() {
|
||||
test('emits ExtensionActivated / ExtensionDeactivated events', () async {
|
||||
final activated = <String>[];
|
||||
final deactivated = <String>[];
|
||||
final s1 = f.services.events
|
||||
.on<ExtensionActivated>()
|
||||
.listen((e) => activated.add(e.id));
|
||||
final s2 = f.services.events
|
||||
.on<ExtensionDeactivated>()
|
||||
.listen((e) => deactivated.add(e.id));
|
||||
final s1 = f.services.events.on<ExtensionActivated>().listen((e) => activated.add(e.id));
|
||||
final s2 = f.services.events.on<ExtensionDeactivated>().listen((e) => deactivated.add(e.id));
|
||||
f.services.extensions.register(_Ext(id: 'e'));
|
||||
await f.services.extensions.activateAll();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
@@ -85,8 +85,7 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('falls through to default-locale when current locale is empty',
|
||||
() async {
|
||||
test('falls through to default-locale when current locale is empty', () async {
|
||||
final i = build(catalogs: {
|
||||
'builtin.x': {
|
||||
const Locale('en', 'US'): {
|
||||
@@ -143,8 +142,7 @@ void main() {
|
||||
expect(i.string('k', namespace: 'b', placeholder: '-'), 'B');
|
||||
});
|
||||
|
||||
test('setLocale refreshes cached namespaces and notifies listeners',
|
||||
() async {
|
||||
test('setLocale refreshes cached namespaces and notifies listeners', () async {
|
||||
final i = build(catalogs: {
|
||||
'x': {
|
||||
const Locale('en', 'US'): {
|
||||
|
||||
@@ -32,10 +32,7 @@ void main() {
|
||||
|
||||
test('honors initialName when present', () {
|
||||
final c = ThemeController(
|
||||
bundled: [
|
||||
_def('a', const Color(0xFF000000)),
|
||||
_def('b', const Color(0xFF999999))
|
||||
],
|
||||
bundled: [_def('a', const Color(0xFF000000)), _def('b', const Color(0xFF999999))],
|
||||
initialName: 'b',
|
||||
);
|
||||
expect(c.currentName, 'b');
|
||||
|
||||
@@ -35,8 +35,7 @@ semantic:
|
||||
mainchrome: red
|
||||
focus: "#123456"
|
||||
''');
|
||||
expect(
|
||||
def.semanticOverride!.lookup('mainchrome'), const Color(0xFFFF0000));
|
||||
expect(def.semanticOverride!.lookup('mainchrome'), const Color(0xFFFF0000));
|
||||
expect(def.semanticOverride!.lookup('focus'), const Color(0xFF123456));
|
||||
});
|
||||
|
||||
|
||||
@@ -126,8 +126,7 @@ void main() {
|
||||
'ext.sqlite.table.background': '#ABCDEF',
|
||||
},
|
||||
);
|
||||
expect(tokens.extensionTokens['ext.sqlite.table.background'],
|
||||
const Color(0xFFABCDEF));
|
||||
expect(tokens.extensionTokens['ext.sqlite.table.background'], const Color(0xFFABCDEF));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -24,8 +24,7 @@ void main() {
|
||||
expect(find.text('Save'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('emits a Semantics node with button: true + label',
|
||||
(tester) async {
|
||||
testWidgets('emits a Semantics node with button: true + label', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
harness(f, ClideButton(label: 'Commit', onPressed: () {})),
|
||||
);
|
||||
@@ -38,8 +37,7 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('semanticLabel overrides the visible label for a11y',
|
||||
(tester) async {
|
||||
testWidgets('semanticLabel overrides the visible label for a11y', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
harness(
|
||||
f,
|
||||
@@ -54,8 +52,7 @@ void main() {
|
||||
expect(semantics.label, 'Save document');
|
||||
});
|
||||
|
||||
testWidgets('semanticHint propagates to the Semantics node',
|
||||
(tester) async {
|
||||
testWidgets('semanticHint propagates to the Semantics node', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
harness(
|
||||
f,
|
||||
@@ -75,8 +72,7 @@ void main() {
|
||||
harness(f, const ClideButton(label: 'Nope', onPressed: null)),
|
||||
);
|
||||
final semantics = tester.getSemantics(find.byType(ClideButton));
|
||||
expect(
|
||||
semantics.getSemanticsData().hasAction(SemanticsAction.tap), isFalse);
|
||||
expect(semantics.getSemanticsData().hasAction(SemanticsAction.tap), isFalse);
|
||||
});
|
||||
|
||||
testWidgets('tap invokes onPressed', (tester) async {
|
||||
|
||||
@@ -11,16 +11,14 @@ void main() {
|
||||
setUp(() async => f = await KernelFixture.create());
|
||||
tearDown(() async => f.dispose());
|
||||
|
||||
testWidgets('renders a 1px horizontal container by default',
|
||||
(tester) async {
|
||||
testWidgets('renders a 1px horizontal container by default', (tester) async {
|
||||
await tester.pumpWidget(harness(f, const ClideDivider()));
|
||||
final c = tester.widget<Container>(find.byType(Container));
|
||||
expect(c.constraints?.maxHeight, 1.0);
|
||||
expect(c.color, f.services.theme.current.surface.dividerColor);
|
||||
});
|
||||
|
||||
testWidgets('vertical axis yields a width-constrained container',
|
||||
(tester) async {
|
||||
testWidgets('vertical axis yields a width-constrained container', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
harness(f, const ClideDivider(axis: Axis.vertical, thickness: 2)),
|
||||
);
|
||||
|
||||
@@ -11,8 +11,7 @@ void main() {
|
||||
setUp(() async => f = await KernelFixture.create());
|
||||
tearDown(() async => f.dispose());
|
||||
|
||||
testWidgets('sizes a SizedBox + CustomPaint to the given size',
|
||||
(tester) async {
|
||||
testWidgets('sizes a SizedBox + CustomPaint to the given size', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
harness(f, const ClideIcon(FolderIcon(), size: 24)),
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user