From b84a436917a9445f80d7135dedc62ba453d46aa8 Mon Sep 17 00:00:00 2001 From: chaos-zhu Date: Sat, 23 May 2026 22:41:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4sftp=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=BC=96=E8=BE=91=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shell/editor/editor_formatters.dart | 99 ------------------- .../shell/editor/editor_language.dart | 17 ---- .../shell/editor/text_editor_controller.dart | 24 ----- .../shell/editor/text_editor_page.dart | 26 ----- mobile/lib/l10n/strings_en.dart | 3 - mobile/lib/l10n/strings_zh.dart | 3 - .../Flutter/GeneratedPluginRegistrant.swift | 2 + mobile/pubspec.lock | 10 +- mobile/pubspec.yaml | 2 - .../shell/editor/editor_formatters_test.dart | 41 -------- .../shell/editor/editor_language_test.dart | 15 +-- .../editor/text_editor_controller_test.dart | 26 ----- 12 files changed, 7 insertions(+), 261 deletions(-) delete mode 100644 mobile/lib/features/shell/editor/editor_formatters.dart delete mode 100644 mobile/test/features/shell/editor/editor_formatters_test.dart diff --git a/mobile/lib/features/shell/editor/editor_formatters.dart b/mobile/lib/features/shell/editor/editor_formatters.dart deleted file mode 100644 index 87118ce..0000000 --- a/mobile/lib/features/shell/editor/editor_formatters.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'dart:convert'; - -import 'package:xml/xml.dart'; -import 'package:yaml/yaml.dart'; - -String formatJson(String src) { - try { - final decoded = jsonDecode(src); - return const JsonEncoder.withIndent(' ').convert(decoded); - } on FormatException { - rethrow; - } -} - -String formatYaml(String src) { - try { - final decoded = loadYaml(src); - final buffer = StringBuffer(); - _dumpYaml(decoded, buffer, 0); - final output = buffer.toString(); - return output.endsWith('\n') ? output : '$output\n'; - } on YamlException catch (err) { - throw FormatException(err.message); - } -} - -String formatXml(String src) { - try { - final doc = XmlDocument.parse(src); - return doc.toXmlString(pretty: true, indent: ' '); - } on XmlException catch (err) { - throw FormatException(err.message); - } -} - -void _dumpYaml(dynamic node, StringBuffer buf, int indent) { - final pad = ' ' * indent; - if (node is YamlMap || node is Map) { - final map = node is YamlMap - ? node.nodes.map((k, v) => MapEntry(k.toString(), v.value)) - : (node as Map); - if (map.isEmpty) { - buf.write('{}\n'); - return; - } - var first = true; - for (final entry in map.entries) { - if (!first || indent > 0) buf.write(pad); - first = false; - buf.write('${_yamlKey(entry.key.toString())}:'); - final v = entry.value; - if (v is YamlMap || v is Map || v is YamlList || v is List) { - buf.write('\n'); - _dumpYaml(v, buf, indent + 1); - } else { - buf.write(' ${_yamlScalar(v)}\n'); - } - } - } else if (node is YamlList || node is List) { - final list = node is YamlList ? node.toList() : (node as List); - if (list.isEmpty) { - buf.write('$pad[]\n'); - return; - } - for (final item in list) { - buf.write('$pad- '); - if (item is YamlMap || item is Map || item is YamlList || item is List) { - buf.write('\n'); - _dumpYaml(item, buf, indent + 1); - } else { - buf.write('${_yamlScalar(item)}\n'); - } - } - } else { - buf.write('$pad${_yamlScalar(node)}\n'); - } -} - -String _yamlKey(String key) { - if (RegExp(r'^[A-Za-z_][\w\-]*$').hasMatch(key)) return key; - return _yamlQuote(key); -} - -String _yamlScalar(dynamic value) { - if (value == null) return 'null'; - if (value is bool) return value.toString(); - if (value is num) return value.toString(); - final s = value.toString(); - if (s.isEmpty) return '""'; - if (RegExp(r'^(true|false|null|~|\d|-)').hasMatch(s) || s.contains(': ') || s.contains('#')) { - return _yamlQuote(s); - } - return s; -} - -String _yamlQuote(String s) { - final escaped = s.replaceAll(r'\', r'\\').replaceAll('"', r'\"'); - return '"$escaped"'; -} diff --git a/mobile/lib/features/shell/editor/editor_language.dart b/mobile/lib/features/shell/editor/editor_language.dart index 4548e3a..bb0d6b3 100644 --- a/mobile/lib/features/shell/editor/editor_language.dart +++ b/mobile/lib/features/shell/editor/editor_language.dart @@ -18,20 +18,17 @@ class EditorLanguage { const EditorLanguage({ required this.id, required this.highlightMode, - required this.formatSupported, required this.defaultIndent, }); final String id; final Mode? highlightMode; - final bool formatSupported; final int defaultIndent; } const _plainText = EditorLanguage( id: 'Plain Text', highlightMode: null, - formatSupported: false, defaultIndent: 2, ); @@ -75,7 +72,6 @@ EditorLanguage detectFromFileName(String name) { return EditorLanguage( id: 'JSON', highlightMode: langJson, - formatSupported: true, defaultIndent: 2, ); case 'yaml': @@ -83,7 +79,6 @@ EditorLanguage detectFromFileName(String name) { return EditorLanguage( id: 'YAML', highlightMode: langYaml, - formatSupported: true, defaultIndent: 2, ); case 'xml': @@ -93,7 +88,6 @@ EditorLanguage detectFromFileName(String name) { return EditorLanguage( id: 'XML', highlightMode: langXml, - formatSupported: ext == 'xml', defaultIndent: 2, ); case 'ts': @@ -101,7 +95,6 @@ EditorLanguage detectFromFileName(String name) { return EditorLanguage( id: 'TypeScript', highlightMode: langTypescript, - formatSupported: false, defaultIndent: 2, ); case 'js': @@ -111,7 +104,6 @@ EditorLanguage detectFromFileName(String name) { return EditorLanguage( id: 'JavaScript', highlightMode: langJavascript, - formatSupported: false, defaultIndent: 2, ); case 'sh': @@ -120,35 +112,30 @@ EditorLanguage detectFromFileName(String name) { return EditorLanguage( id: 'Bash', highlightMode: langBash, - formatSupported: false, defaultIndent: 2, ); case 'py': return EditorLanguage( id: 'Python', highlightMode: langPython, - formatSupported: false, defaultIndent: 4, ); case 'go': return EditorLanguage( id: 'Go', highlightMode: langGo, - formatSupported: false, defaultIndent: 2, ); case 'sql': return EditorLanguage( id: 'SQL', highlightMode: langSql, - formatSupported: false, defaultIndent: 2, ); case 'dart': return EditorLanguage( id: 'Dart', highlightMode: langDart, - formatSupported: false, defaultIndent: 2, ); case 'md': @@ -156,7 +143,6 @@ EditorLanguage detectFromFileName(String name) { return EditorLanguage( id: 'Markdown', highlightMode: langMarkdown, - formatSupported: false, defaultIndent: 2, ); case 'ini': @@ -166,21 +152,18 @@ EditorLanguage detectFromFileName(String name) { return EditorLanguage( id: 'INI', highlightMode: langIni, - formatSupported: false, defaultIndent: 2, ); case 'dockerfile': return EditorLanguage( id: 'Dockerfile', highlightMode: langDockerfile, - formatSupported: false, defaultIndent: 2, ); case 'nginx': return EditorLanguage( id: 'Nginx', highlightMode: langNginx, - formatSupported: false, defaultIndent: 2, ); default: diff --git a/mobile/lib/features/shell/editor/text_editor_controller.dart b/mobile/lib/features/shell/editor/text_editor_controller.dart index eabf4d8..dd53fd0 100644 --- a/mobile/lib/features/shell/editor/text_editor_controller.dart +++ b/mobile/lib/features/shell/editor/text_editor_controller.dart @@ -1,7 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:re_editor/re_editor.dart'; -import 'editor_formatters.dart'; import 'editor_language.dart'; abstract class TextEditorWriter { @@ -33,7 +32,6 @@ class TextEditorController extends ChangeNotifier { bool get isDirty => code.text != _originalText; bool get saving => _saving; - bool get canFormat => language.formatSupported; void _onCodeChanged() { notifyListeners(); @@ -55,28 +53,6 @@ class TextEditorController extends ChangeNotifier { } } - void format() { - if (!language.formatSupported) return; - final src = code.text; - final String formatted; - switch (language.id) { - case 'JSON': - formatted = formatJson(src); - break; - case 'YAML': - formatted = formatYaml(src); - break; - case 'XML': - formatted = formatXml(src); - break; - default: - return; - } - if (formatted != src) { - code.text = formatted; - } - } - @override void dispose() { code.removeListener(_onCodeChanged); diff --git a/mobile/lib/features/shell/editor/text_editor_page.dart b/mobile/lib/features/shell/editor/text_editor_page.dart index 87e062e..65a4b81 100644 --- a/mobile/lib/features/shell/editor/text_editor_page.dart +++ b/mobile/lib/features/shell/editor/text_editor_page.dart @@ -456,12 +456,6 @@ class _TextEditorPageState extends ConsumerState { ), ), const Spacer(), - TextButton.icon( - icon: const Icon(Icons.auto_fix_high, size: 16), - label: Text(l.tr('editor.format')), - onPressed: controller.canFormat ? () => _onFormat(context, controller) : null, - ), - const SizedBox(width: 8), FilledButton.icon( icon: controller.saving ? const SizedBox( @@ -495,26 +489,6 @@ class _TextEditorPageState extends ConsumerState { } } - void _onFormat(BuildContext context, TextEditorController controller) { - final l = AppLocalizations.of(context); - if (!controller.canFormat) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(l.tr('editor.formatUnsupported')))); - return; - } - try { - controller.format(); - } on FormatException catch (error) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l.trf('editor.formatFailed', [error.message]))), - ); - } catch (error) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l.trf('editor.formatFailed', [error.toString()]))), - ); - } - } - Future _openSettings(BuildContext context, _EditorPalette palette) { return showModalBottomSheet( context: context, diff --git a/mobile/lib/l10n/strings_en.dart b/mobile/lib/l10n/strings_en.dart index 726fc04..98dbede 100644 --- a/mobile/lib/l10n/strings_en.dart +++ b/mobile/lib/l10n/strings_en.dart @@ -210,7 +210,6 @@ const Map stringsEn = { 'editor.saveFailed': 'Save failed: {0}', 'editor.saved': 'Saved', 'editor.unsaved': 'Unsaved', - 'editor.format': 'Format', 'editor.save': 'Save', 'editor.undo': 'Undo', 'editor.redo': 'Redo', @@ -220,8 +219,6 @@ const Map stringsEn = { 'editor.discardLeave': 'Discard', 'editor.discardSaveAndLeave': 'Save & leave', 'editor.malformedUtf8': 'File contains non-UTF-8 bytes; saving may lose some characters.', - 'editor.formatUnsupported': 'Format not supported for this language.', - 'editor.formatFailed': 'Format failed: {0}', 'editor.statusEncoding': 'UTF-8 · LF · {0}', 'editor.statusPosition': 'Ln {0}, Col {1}', 'editor.statusLineCount': '{0} / {1}', diff --git a/mobile/lib/l10n/strings_zh.dart b/mobile/lib/l10n/strings_zh.dart index 1b720c0..d89e4d3 100644 --- a/mobile/lib/l10n/strings_zh.dart +++ b/mobile/lib/l10n/strings_zh.dart @@ -199,7 +199,6 @@ const Map stringsZh = { 'editor.saveFailed': '保存失败:{0}', 'editor.saved': '已保存', 'editor.unsaved': '未保存', - 'editor.format': '格式化', 'editor.save': '保存', 'editor.undo': '撤销', 'editor.redo': '重做', @@ -209,8 +208,6 @@ const Map stringsZh = { 'editor.discardLeave': '放弃', 'editor.discardSaveAndLeave': '保存并退出', 'editor.malformedUtf8': '文件含非 UTF-8 字节,保存可能丢失部分字符', - 'editor.formatUnsupported': '当前语言不支持格式化', - 'editor.formatFailed': '格式化失败:{0}', 'editor.statusEncoding': 'UTF-8 · LF · {0}', 'editor.statusPosition': 'Ln {0}, Col {1}', 'editor.statusLineCount': '{0} / {1}', diff --git a/mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift index 668464a..27a7e25 100644 --- a/mobile/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,14 @@ import Foundation import file_picker import flutter_secure_storage_macos +import path_provider_foundation import share_plus import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 8774043..344193f 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -843,7 +843,7 @@ packages: source: hosted version: "1.1.0" xml: - dependency: "direct main" + dependency: transitive description: name: xml sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 @@ -858,14 +858,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" - yaml: - dependency: "direct main" - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" zmodem: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 2a01111..84dc194 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -30,8 +30,6 @@ dependencies: share_plus: ^12.0.2 re_editor: ^0.8.0 re_highlight: ^0.0.3 - yaml: ^3.1.2 - xml: ^6.5.0 dev_dependencies: flutter_test: diff --git a/mobile/test/features/shell/editor/editor_formatters_test.dart b/mobile/test/features/shell/editor/editor_formatters_test.dart deleted file mode 100644 index ebaca67..0000000 --- a/mobile/test/features/shell/editor/editor_formatters_test.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:mobile/features/shell/editor/editor_formatters.dart'; - -void main() { - group('formatJson', () { - test('pretty-prints with 2-space indent', () { - final out = formatJson('{"a":1,"b":[1,2,3]}'); - expect(out, contains('\n "a": 1')); - expect(out, contains(' 1')); - }); - - test('throws FormatException on invalid JSON', () { - expect(() => formatJson('{not json'), throwsA(isA())); - }); - }); - - group('formatYaml', () { - test('round-trips a simple map with 2-space indent', () { - final out = formatYaml('foo: 1\nbar:\n baz: hello'); - expect(out, contains('foo: 1')); - expect(out, contains('bar:')); - expect(out, contains(' baz: hello')); - }); - - test('throws FormatException on invalid YAML', () { - expect(() => formatYaml(': : : not yaml'), throwsA(isA())); - }); - }); - - group('formatXml', () { - test('pretty-prints valid xml with 2-space indent', () { - final out = formatXml('x'); - expect(out, contains('')); - expect(out, contains(' x')); - }); - - test('throws FormatException on invalid XML', () { - expect(() => formatXml(''), throwsA(isA())); - }); - }); -} diff --git a/mobile/test/features/shell/editor/editor_language_test.dart b/mobile/test/features/shell/editor/editor_language_test.dart index cb0504b..e85e4b8 100644 --- a/mobile/test/features/shell/editor/editor_language_test.dart +++ b/mobile/test/features/shell/editor/editor_language_test.dart @@ -3,10 +3,9 @@ import 'package:mobile/features/shell/editor/editor_language.dart'; void main() { group('detectFromFileName', () { - test('maps .json to JSON with formatter and highlight', () { + test('maps .json to JSON with highlight', () { final lang = detectFromFileName('config.json'); expect(lang.id, 'JSON'); - expect(lang.formatSupported, isTrue); expect(lang.highlightMode, isNotNull); expect(lang.defaultIndent, 2); }); @@ -14,32 +13,26 @@ void main() { test('maps .yaml and .yml to YAML', () { expect(detectFromFileName('a.yaml').id, 'YAML'); expect(detectFromFileName('a.yml').id, 'YAML'); - expect(detectFromFileName('a.yaml').formatSupported, isTrue); }); test('maps .xml to XML', () { - final lang = detectFromFileName('pom.xml'); - expect(lang.id, 'XML'); - expect(lang.formatSupported, isTrue); + expect(detectFromFileName('pom.xml').id, 'XML'); }); - test('maps .ts to TypeScript without formatter support', () { + test('maps .ts to TypeScript', () { final lang = detectFromFileName('app.ts'); expect(lang.id, 'TypeScript'); - expect(lang.formatSupported, isFalse); expect(lang.highlightMode, isNotNull); }); - test('maps .sh to Bash without formatter support', () { + test('maps .sh to Bash', () { final lang = detectFromFileName('deploy.sh'); expect(lang.id, 'Bash'); - expect(lang.formatSupported, isFalse); }); test('unknown extension falls back to plaintext with no highlight', () { final lang = detectFromFileName('notes.unknownext'); expect(lang.id, 'Plain Text'); - expect(lang.formatSupported, isFalse); expect(lang.highlightMode, isNull); }); diff --git a/mobile/test/features/shell/editor/text_editor_controller_test.dart b/mobile/test/features/shell/editor/text_editor_controller_test.dart index 4e704f9..c420a48 100644 --- a/mobile/test/features/shell/editor/text_editor_controller_test.dart +++ b/mobile/test/features/shell/editor/text_editor_controller_test.dart @@ -63,31 +63,5 @@ void main() { expect(controller.isDirty, isTrue); controller.dispose(); }); - - test('format applies JSON formatter when supported', () { - final controller = TextEditorController( - writer: _FakeWriter(), - remotePath: '/etc/app.json', - originalText: '{"a":1,"b":2}', - language: detectFromFileName('app.json'), - totalBytes: 14, - ); - controller.code.text = '{"a":1,"b":2}'; - controller.format(); - expect(controller.code.text, contains('\n "a": 1')); - controller.dispose(); - }); - - test('canFormat is false for plaintext', () { - final controller = TextEditorController( - writer: _FakeWriter(), - remotePath: '/etc/notes', - originalText: 'plain', - language: detectFromFileName('notes'), - totalBytes: 5, - ); - expect(controller.canFormat, isFalse); - controller.dispose(); - }); }); }