diff --git a/CHANGELOG.md b/CHANGELOG.md index a376b98..6bc8166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -381,4 +381,7 @@ This release establishes **CodeForge** as a powerful, production-ready code edit - Enhanced large text handling. ## 9.8.0 - - - FIX: LSP initialization bug. \ No newline at end of file + - FIX: LSP initialization bug. + +## 9.9.0 + - FEATURE: add customize `tabSize` to controller (default 4) diff --git a/README.md b/README.md index 62f8481..3d9a811 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,9 @@ This will make the code_forge lightning fast like the [zed editor](https://zed.dev/).** -### What's new in 9.8.0 - - FIX: LSP initialization bug. +### What's new in 9.9.0 + - FIX: Cursor jump on typing. + - FIX: Frozen horizontal scroll on dynamic font size. @@ -122,7 +123,7 @@ Add CodeForge to your `pubspec.yaml`: ```yaml dependencies: - code_forge: ^9.8.0 + code_forge: ^9.9.0 ``` Then run: @@ -733,15 +734,9 @@ CodeForge supports a variety of keyboard shortcuts for efficient editing: --- -## Contributing +## Used by -Contributions are welcome! Whether it's bug fixes, new features, or documentation improvements. - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request + - [ROXUM IDE](https://github.com/heckmon/roxum-ide) - A minimal and powerful IDE/Code editor for Android. --- diff --git a/analysis_options.yaml b/analysis_options.yaml index a5744c1..b72f386 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,41 @@ include: package:flutter_lints/flutter.yaml -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +analyzer: + exclude: + - "lib/generated/**" + - "**.g.dart" + + errors: + unused_import: warning + unused_local_variable: warning + dead_code: warning + deprecated_member_use: ignore + avoid_print: warning + +linter: + rules: + - directives_ordering + - prefer_relative_imports + + - use_super_parameters + - unnecessary_this + - unnecessary_new + - prefer_is_empty + - prefer_is_not_empty + - avoid_unnecessary_containers + - sized_box_for_whitespace + - unnecessary_string_interpolations + - prefer_collection_literals + + - prefer_const_constructors + - prefer_const_declarations + - prefer_const_constructors_in_immutables + - prefer_const_literals_to_create_immutables + + - prefer_single_quotes + - require_trailing_commas + - sort_child_properties_last + - prefer_final_fields + - prefer_final_locals + - avoid_empty_else + - use_build_context_synchronously diff --git a/example/lib/main.dart b/example/lib/main.dart index 3f3d85b..c71db5e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -78,6 +78,7 @@ class _MyAppState extends State { controller: codeController, textStyle: GoogleFonts.jetBrainsMono(), filePath: absFilePath, + tapSize: 5, matchHighlightStyle: const MatchHighlightStyle( currentMatchStyle: TextStyle( backgroundColor: Color(0xFFFFA726), diff --git a/example/pubspec.lock b/example/pubspec.lock index 55e23e3..a922682 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -47,7 +47,7 @@ packages: path: ".." relative: true source: path - version: "9.5.0" + version: "9.9.0" collection: dependency: transitive description: @@ -68,10 +68,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" fake_async: dependency: transitive description: diff --git a/lib/LSP/lsp.dart b/lib/LSP/lsp.dart index 437f20d..b7fbb0b 100644 --- a/lib/LSP/lsp.dart +++ b/lib/LSP/lsp.dart @@ -71,7 +71,7 @@ sealed class LspConfig { this.disableError = false, }) { _initOptions.addAll({ - "highlight": {'enabled': true}, + 'highlight': {'enabled': true}, ...initializationOptions, }); } @@ -239,7 +239,6 @@ sealed class LspConfig { /// /// This method is used internally by the [CodeForge] widget and calling it directly is not recommended. /// - /// If [initialContent] is provided, it will be used as the document content. /// Otherwise, the content will be read from [filePath]. Future openDocument(String filePath) async { final version = (_openDocuments[filePath] ?? 0) + 1; @@ -256,7 +255,7 @@ sealed class LspConfig { }, }, ); - await Future.delayed(Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 300)); } /// Updates the document content in the LSP server. @@ -336,7 +335,7 @@ sealed class LspConfig { int character, ) async { if (!capabilities.codeCompletion) return []; - List completion = []; + final List completion = []; final response = await sendRequest( method: 'textDocument/completion', params: _commonParams(filePath, line, character), @@ -369,13 +368,13 @@ sealed class LspConfig { orElse: () => CompletionItemType.text, ), importUri: importUriList, - reference: item["data"]?["ref"], + reference: item['data']?['ref'], completionItem: item, ), ); } } on Exception catch (e) { - debugPrint("An Error Occured: $e"); + debugPrint('An Error Occured: $e'); } return completion; } @@ -456,11 +455,11 @@ sealed class LspConfig { bool isRetrigger = false, }) async { if (!capabilities.signatureHelp) { - return LspSignatureHelps( + return const LspSignatureHelps( activeParameter: -1, activeSignature: -1, - documentation: "", - label: "", + documentation: '', + label: '', parameters: [], ); } @@ -468,7 +467,7 @@ sealed class LspConfig { commonParams.addAll({ 'context': { 'triggerKind': triggerKind, - if (triggerCharacter != null) 'triggerCharacter': triggerCharacter, + 'triggerCharacter': ?triggerCharacter, 'isRetrigger': isRetrigger, }, }); @@ -480,11 +479,11 @@ sealed class LspConfig { final result = response['result']; if (result == null) { - return LspSignatureHelps( + return const LspSignatureHelps( activeParameter: -1, activeSignature: -1, - documentation: "", - label: "", + documentation: '', + label: '', parameters: [], ); } @@ -494,7 +493,7 @@ sealed class LspConfig { late final List> parameters; if (signatures is List) { if (signatures.isNotEmpty) { - label = (signatures[0]['label']) ?? ""; + label = (signatures[0]['label']) ?? ''; final rawParameters = signatures[0]['parameters']; if (rawParameters is List) { parameters = rawParameters.map((param) { @@ -514,18 +513,18 @@ sealed class LspConfig { if (docField is String) { doc = docField; } else if (docField is Map) { - doc = docField['value'] ?? ""; + doc = docField['value'] ?? ''; } else { - doc = ""; + doc = ''; } } else { - label = ""; - doc = ""; + label = ''; + doc = ''; parameters = []; } } else { - label = ""; - doc = ""; + label = ''; + doc = ''; parameters = []; } return LspSignatureHelps( @@ -577,7 +576,7 @@ sealed class LspConfig { /// should inspect `response['result']` according to the server's /// specification for color presentations. final response = await sendRequest( - method: "textDocument/colorPresentation", + method: 'textDocument/colorPresentation', params: { 'textDocument': {'uri': Uri.file(filePath).toString()}, 'color': {'red': red, 'blue': blue, 'green': green, 'alpha': alpha}, @@ -597,7 +596,7 @@ sealed class LspConfig { Future> getDocumentColor(String filePath) async { if (!capabilities.documentColor) return {'result': []}; final response = await sendRequest( - method: "textDocument/documentColor", + method: 'textDocument/documentColor', params: { 'textDocument': {'uri': Uri.file(filePath).toString()}, }, @@ -614,7 +613,7 @@ sealed class LspConfig { Future> getLSPFoldRanges(String filePath) async { if (!capabilities.codeFolding) return {'result': []}; final response = await sendRequest( - method: "textDocument/foldingRange", + method: 'textDocument/foldingRange', params: { 'textDocument': {'uri': Uri.file(filePath).toString()}, }, @@ -639,7 +638,7 @@ sealed class LspConfig { ) async { if (!capabilities.inlayHint) return {'result': []}; final response = await sendRequest( - method: "textDocument/inlayHint", + method: 'textDocument/inlayHint', params: { 'textDocument': {'uri': Uri.file(filePath).toString()}, 'range': { @@ -851,7 +850,7 @@ sealed class LspConfig { }, 'context': { 'diagnostics': diagnostics, - if (Platform.isAndroid && filePath.endsWith(".java")) + if (Platform.isAndroid && filePath.endsWith('.java')) 'only': [ 'quickfix', 'refactor.extract', @@ -1076,63 +1075,87 @@ enum CompletionItemType { } Map completionItemIcons = { - CompletionItemType.text: Icon(Icons.text_snippet_rounded, color: Colors.grey), - CompletionItemType.method: Icon( + CompletionItemType.text: const Icon( + Icons.text_snippet_rounded, + color: Colors.grey, + ), + CompletionItemType.method: const Icon( CustomIcons.method, - color: const Color(0xff9e74c0), + color: Color(0xff9e74c0), ), - CompletionItemType.function: Icon( + CompletionItemType.function: const Icon( CustomIcons.method, - color: const Color(0xff9e74c0), + color: Color(0xff9e74c0), ), - CompletionItemType.constructor: Icon( + CompletionItemType.constructor: const Icon( CustomIcons.method, - color: const Color(0xff9e74c0), + color: Color(0xff9e74c0), ), - CompletionItemType.field: Icon( + CompletionItemType.field: const Icon( CustomIcons.field, - color: const Color(0xff75beff), + color: Color(0xff75beff), ), - CompletionItemType.variable: Icon( + CompletionItemType.variable: const Icon( CustomIcons.variable, - color: const Color(0xff75beff), + color: Color(0xff75beff), ), - CompletionItemType.class_: Icon( + CompletionItemType.class_: const Icon( CustomIcons.class_, - color: const Color(0xffee9d28), + color: Color(0xffee9d28), + ), + CompletionItemType.interface: const Icon( + CustomIcons.interface, + color: Colors.grey, + ), + CompletionItemType.module: const Icon( + Icons.folder_special, + color: Colors.grey, + ), + CompletionItemType.property: const Icon(Icons.build, color: Colors.grey), + CompletionItemType.unit: const Icon(Icons.view_module, color: Colors.grey), + CompletionItemType.value_: const Icon(Icons.numbers, color: Colors.grey), + CompletionItemType.enum_: const Icon(Icons.notes, color: Color(0xffee9d28)), + CompletionItemType.keyword: const Icon( + Icons.wysiwyg_rounded, + color: Colors.grey, ), - CompletionItemType.interface: Icon(CustomIcons.interface, color: Colors.grey), - CompletionItemType.module: Icon(Icons.folder_special, color: Colors.grey), - CompletionItemType.property: Icon(Icons.build, color: Colors.grey), - CompletionItemType.unit: Icon(Icons.view_module, color: Colors.grey), - CompletionItemType.value_: Icon(Icons.numbers, color: Colors.grey), - CompletionItemType.enum_: Icon(Icons.notes, color: const Color(0xffee9d28)), - CompletionItemType.keyword: Icon(Icons.wysiwyg_rounded, color: Colors.grey), - CompletionItemType.snippet: Icon(Icons.rounded_corner, color: Colors.grey), - CompletionItemType.color: Icon(Icons.color_lens, color: Colors.grey), - CompletionItemType.file: Icon(Icons.insert_drive_file, color: Colors.grey), - CompletionItemType.reference: Icon(CustomIcons.reference, color: Colors.grey), - CompletionItemType.folder: Icon(Icons.folder, color: Colors.grey), - CompletionItemType.enumMember: Icon( + CompletionItemType.snippet: const Icon( + Icons.rounded_corner, + color: Colors.grey, + ), + CompletionItemType.color: const Icon(Icons.color_lens, color: Colors.grey), + CompletionItemType.file: const Icon( + Icons.insert_drive_file, + color: Colors.grey, + ), + CompletionItemType.reference: const Icon( + CustomIcons.reference, + color: Colors.grey, + ), + CompletionItemType.folder: const Icon(Icons.folder, color: Colors.grey), + CompletionItemType.enumMember: const Icon( Icons.notes, - color: const Color(0xff75beff), + color: Color(0xff75beff), ), - CompletionItemType.constant: Icon( + CompletionItemType.constant: const Icon( CustomIcons.variable, - color: const Color(0xff75beff), + color: Color(0xff75beff), ), - CompletionItemType.struct: Icon( + CompletionItemType.struct: const Icon( CustomIcons.struct, - color: const Color(0xff75beff), + color: Color(0xff75beff), ), - CompletionItemType.event: Icon( + CompletionItemType.event: const Icon( CustomIcons.event, - color: const Color(0xffee9d28), + color: Color(0xffee9d28), + ), + CompletionItemType.operator: const Icon( + CustomIcons.operator, + color: Colors.grey, ), - CompletionItemType.operator: Icon(CustomIcons.operator, color: Colors.grey), - CompletionItemType.typeParameter: Icon( + CompletionItemType.typeParameter: const Icon( CustomIcons.parameter, - color: const Color(0xffee9d28), + color: Color(0xffee9d28), ), }; diff --git a/lib/LSP/lsp_socket.dart b/lib/LSP/lsp_socket.dart index e36fb04..6c6cb91 100644 --- a/lib/LSP/lsp_socket.dart +++ b/lib/LSP/lsp_socket.dart @@ -88,7 +88,7 @@ class LspSocketConfig extends LspConfig { int id, List result, ) async { - final request = {'jsonrpc': '2.0', 'id': id, "result": result}; + final request = {'jsonrpc': '2.0', 'id': id, 'result': result}; _channel.sink.add(jsonEncode(request)); return request; diff --git a/lib/code_forge.dart b/lib/code_forge.dart index 9b41c93..e3f0c25 100644 --- a/lib/code_forge.dart +++ b/lib/code_forge.dart @@ -1,9 +1,9 @@ library; +export 'LSP/lsp.dart'; export 'code_forge/code_area.dart'; export 'code_forge/controller.dart'; -export 'code_forge/styling.dart'; +export 'code_forge/find_controller.dart'; export 'code_forge/scroll.dart'; +export 'code_forge/styling.dart'; export 'code_forge/undo_redo.dart'; -export 'LSP/lsp.dart'; -export 'code_forge/find_controller.dart'; diff --git a/lib/code_forge/code_area.dart b/lib/code_forge/code_area.dart index f945ee4..dacbe93 100644 --- a/lib/code_forge/code_area.dart +++ b/lib/code_forge/code_area.dart @@ -3,7 +3,16 @@ import 'dart:io'; import 'dart:math'; import 'dart:ui' as ui; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; +import 'package:markdown_widget/markdown_widget.dart'; +import 'package:re_highlight/re_highlight.dart'; import 'package:re_highlight/styles/lightfair.dart'; +import 'package:vector_math/vector_math_64.dart' show Vector3; import '../LSP/lsp.dart'; import 'controller.dart'; @@ -13,16 +22,6 @@ import 'styling.dart'; import 'syntax_highlighter.dart'; import 'undo_redo.dart'; -import 'package:re_highlight/re_highlight.dart'; -import 'package:markdown_widget/markdown_widget.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_colorpicker/flutter_colorpicker.dart'; -import 'package:vector_math/vector_math_64.dart' show Vector3; - const String _wordCharPattern = r'[\w\u0600-\u06FF\u08A0-\u08FF\u0590-\u05FF]'; /// A highly customizable code editor widget for Flutter. @@ -153,6 +152,9 @@ class CodeForge extends StatefulWidget { /// When true, the user cannot modify the text content. final bool readOnly; + // Custom tap size + final int tapSize; + /// Whether to wrap long lines. /// /// When true, lines wrap at the editor boundary. When false, @@ -249,6 +251,7 @@ class CodeForge extends StatefulWidget { this.innerPadding, this.customCodeSnippets, this.readOnly = false, + this.tapSize = 4, this.autoFocus = false, this.lineWrap = false, this.enableFolding = true, @@ -305,7 +308,10 @@ class _CodeForgeState extends State with TickerProviderStateMixin { late final VoidCallback _isHoveringPopupListener, _selectedSuggestionListener; late final VoidCallback _snippetSuggestionsListener, _snippetNotifierListener; late bool _readOnly; - final ValueNotifier _offsetNotifier = ValueNotifier(Offset(0, 0)); + late int _tapSize; + final ValueNotifier _offsetNotifier = ValueNotifier( + const Offset(0, 0), + ); final ValueNotifier _lspActionOffsetNotifier = ValueNotifier(null); final _isMobile = Platform.isAndroid || Platform.isIOS; final _suggScrollController = ScrollController(); @@ -353,6 +359,8 @@ class _CodeForgeState extends State with TickerProviderStateMixin { _undoRedoController = widget.undoController ?? UndoRedoController(); _filePath = widget.filePath; _readOnly = widget.readOnly; + _tapSize = widget.tapSize; + _controller.tapSize = _tapSize; _deleteFoldRangeOnDeletingFirstLine = widget.deleteFoldRangeOnDeletingFirstLine; _controller.setUndoController(_undoRedoController); @@ -379,14 +387,14 @@ class _CodeForgeState extends State with TickerProviderStateMixin { SuggestionStyle( elevation: 8, textStyle: (() { - TextStyle style = widget.textStyle ?? TextStyle(); + TextStyle style = widget.textStyle ?? const TextStyle(); if (style.color == null) { style = style.copyWith(color: _editorTheme['root']!.color); } return style; })(), backgroundColor: (() { - final lightnessDelta = 0.03; + const lightnessDelta = 0.03; final base = _editorTheme['root']!.backgroundColor!; final hsl = HSLColor.fromColor(base); final newLightness = (hsl.lightness + lightnessDelta).clamp( @@ -395,20 +403,20 @@ class _CodeForgeState extends State with TickerProviderStateMixin { ); return hsl.withLightness(newLightness).toColor(); })(), - focusColor: ui.Color.fromARGB(108, 2, 66, 129), + focusColor: const ui.Color.fromARGB(108, 2, 66, 129), hoverColor: Colors.grey.withAlpha(15), splashColor: Colors.blueAccent.withAlpha(50), - selectedBackgroundColor: Color(0xFF094771), + selectedBackgroundColor: const Color(0xFF094771), borderColor: _editorTheme['root']!.color?.withAlpha(50) ?? Colors.grey[400], borderWidth: 1.0, itemHeight: 24.0, iconSize: 16.0, - methodIconColor: Color(0xFFDCDFE4), - propertyIconColor: Color(0xFF98C379), - classIconColor: Color(0xFFE06C75), - variableIconColor: Color(0xFF61AFEF), - keywordIconColor: Color(0xFFC678DD), + methodIconColor: const Color(0xFFDCDFE4), + propertyIconColor: const Color(0xFF98C379), + classIconColor: const Color(0xFFE06C75), + variableIconColor: const Color(0xFF61AFEF), + keywordIconColor: const Color(0xFFC678DD), labelTextStyle: TextStyle( fontSize: widget.textStyle?.fontSize ?? 14, fontWeight: FontWeight.w500, @@ -444,7 +452,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { ), ), backgroundColor: (() { - final lightnessDelta = 0.03; + const lightnessDelta = 0.03; final base = _editorTheme['root']!.backgroundColor!; final hsl = HSLColor.fromColor(base); final newLightness = (hsl.lightness + lightnessDelta).clamp( @@ -517,8 +525,8 @@ class _CodeForgeState extends State with TickerProviderStateMixin { if (_filePath == null && _controller.lspConfig != null) { throw ArgumentError( - "The `filePath` parameter cannot be null inorder to use `LspConfig`." - "A valid file path is required to use the LSP services.", + 'The `filePath` parameter cannot be null inorder to use `LspConfig`.' + 'A valid file path is required to use the LSP services.', ); } @@ -1214,7 +1222,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { return; } - int caret = selection.extentOffset; + final int caret = selection.extentOffset; if (caret <= 0) return; final prevChar = text[caret - 1]; @@ -1248,7 +1256,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { return; } - int caret = selection.extentOffset; + final int caret = selection.extentOffset; if (caret >= text.length) return; final after = text.substring(caret); @@ -1266,7 +1274,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { void _moveWordLeft(bool withShift) { final selection = _controller.selection; final text = _controller.text; - int caret = selection.extentOffset; + final int caret = selection.extentOffset; if (caret <= 0) return; @@ -1309,7 +1317,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { void _moveWordRight(bool withShift) { final selection = _controller.selection; final text = _controller.text; - int caret = selection.extentOffset; + final int caret = selection.extentOffset; if (caret >= text.length) return; @@ -2303,21 +2311,21 @@ class _CodeForgeState extends State with TickerProviderStateMixin { _buildContextMenu(), ValueListenableBuilder( valueListenable: _offsetNotifier, - builder: (_, offset, __) { + builder: (_, offset, _) { return ValueListenableBuilder( valueListenable: _lspSignatureNotifier, - builder: (_, signature, __) { + builder: (_, signature, _) { if (signature == null || signature.activeParameter < 0 || signature.parameters.isEmpty) { - return SizedBox.shrink(); + return const SizedBox.shrink(); } final sigScrollCtrl = ScrollController(); final desiredWidth = screenWidth < 700 ? screenWidth * 0.63 : 420.0; - final maxBoxHeight = 400.0; + const maxBoxHeight = 400.0; final fontSize = widget.textStyle?.fontSize ?? 14; double adjustedLeft = offset.dx; @@ -2421,7 +2429,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { TextSpan(text: firstPart), TextSpan( text: highlightPart, - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.blue, @@ -2462,7 +2470,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { TextSpan(text: firstPart), TextSpan( text: highlightPart, - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.blue, @@ -2561,18 +2569,18 @@ class _CodeForgeState extends State with TickerProviderStateMixin { if (offset.dy < 0 || offset.dx < 0 || !widget.enableSuggestions) { - return SizedBox.shrink(); + return const SizedBox.shrink(); } return ValueListenableBuilder( valueListenable: _suggestionNotifier, builder: (_, sugg, child) { if (_aiNotifier.value != null) { - return SizedBox.shrink(); + return const SizedBox.shrink(); } if (sugg == null || sugg.isEmpty) { _sugSelIndex = 0; _controller.currentlySelectedSuggestion = null; - return SizedBox.shrink(); + return const SizedBox.shrink(); } final completionScrlCtrl = ScrollController(); final desiredWidth = screenWidth < 700 @@ -2621,7 +2629,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { bottom: adjustedBottom, left: adjustedLeft, child: ConstrainedBox( - constraints: BoxConstraints( + constraints: const BoxConstraints( maxHeight: 400, maxWidth: 400, minWidth: 70, @@ -2716,7 +2724,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { }); } catch (e) { debugPrint( - "Completion Resolve failed: ${e.toString()}", + 'Completion Resolve failed: ${e.toString()}', ); } })(); @@ -2761,10 +2769,11 @@ class _CodeForgeState extends State with TickerProviderStateMixin { return Container( height: _suggestionStyle.itemHeight, - padding: EdgeInsets.symmetric( - horizontal: 8, - vertical: 2, - ), + padding: + const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), decoration: BoxDecoration( color: ((!_isMobile && @@ -3024,7 +3033,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { : adjustedLeft + suggestionWidth), child: ConstrainedBox( - constraints: BoxConstraints( + constraints: const BoxConstraints( maxWidth: 420, maxHeight: 400, minWidth: 70, @@ -3115,7 +3124,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { if (hov == null || _controller.lspConfig == null || !widget.enableSuggestions) { - return SizedBox.shrink(); + return const SizedBox.shrink(); } final Offset position = hov.$1; final width = _isMobile @@ -3155,7 +3164,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { onExit: (_) => _isHoveringPopup.value = false, child: ValueListenableBuilder?>( valueListenable: _hoverContentNotifier, - builder: (_, data, __) { + builder: (_, data, _) { if (data == null) { return ConstrainedBox( constraints: BoxConstraints( @@ -3168,7 +3177,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { child: Padding( padding: const EdgeInsets.all(8.0), child: Text( - "Loading...", + 'Loading...', style: _hoverDetailsStyle.textStyle, ), ), @@ -3183,7 +3192,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { if (diagnosticMessage.isEmpty && hoverMessage.isEmpty) { - return SizedBox.shrink(); + return const SizedBox.shrink(); } IconData diagnosticIcon; @@ -3260,7 +3269,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { color: diagnosticColor, size: 16, ), - SizedBox(width: 8), + const SizedBox(width: 8), Text( diagnosticMessage, style: _hoverDetailsStyle @@ -3305,7 +3314,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { .lspConfig ?.languageId .toLowerCase() ?? - "dart", + 'dart', theme: _editorTheme, textStyle: TextStyle( fontSize: @@ -3382,12 +3391,12 @@ class _CodeForgeState extends State with TickerProviderStateMixin { decoration: BoxDecoration( color: _editorTheme['root'] ?.backgroundColor, - borderRadius: BorderRadius.all( + borderRadius: const BorderRadius.all( Radius.circular(8), ), border: BoxBorder.all( width: 1.5, - color: Color(0xff64b5f6), + color: const Color(0xff64b5f6), ), ), child: Icon( @@ -3396,7 +3405,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { ), ), ), - SizedBox(width: 30), + const SizedBox(width: 30), InkWell( onTap: () { _aiNotifier.value = null; @@ -3406,7 +3415,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { decoration: BoxDecoration( color: _editorTheme['root'] ?.backgroundColor, - borderRadius: BorderRadius.all( + borderRadius: const BorderRadius.all( Radius.circular(8), ), border: BoxBorder.all( @@ -3423,7 +3432,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { ], ), ) - : SizedBox.shrink(); + : const SizedBox.shrink(); }, ), ValueListenableBuilder( @@ -3433,7 +3442,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { _lspActionNotifier.value == null || _controller.lspConfig == null || !widget.enableSuggestions) { - return SizedBox.shrink(); + return const SizedBox.shrink(); } return Positioned( @@ -3444,7 +3453,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { offset.dy + (widget.textStyle?.fontSize ?? 14) + 10, left: offset.dx, child: ConstrainedBox( - constraints: BoxConstraints( + constraints: const BoxConstraints( maxHeight: 400, maxWidth: 400, minWidth: 70, @@ -3479,7 +3488,7 @@ class _CodeForgeState extends State with TickerProviderStateMixin { borderRadius: BorderRadius.circular(3), ), height: _suggestionStyle.itemHeight, - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), @@ -6000,7 +6009,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { final fontSize = textStyle?.fontSize ?? 14.0; final colorBoxSize = fontSize * 0.85; - final colorBoxSpacing = 4.0; + const colorBoxSpacing = 4.0; final totalColorWidth = colorBoxSize + colorBoxSpacing; double offset = 0; @@ -6065,7 +6074,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { if (colors.isNotEmpty) { final fontSize = textStyle?.fontSize ?? 14.0; final colorBoxSize = fontSize * 0.85; - final colorBoxSpacing = 4.0; + const colorBoxSpacing = 4.0; final totalColorWidth = colorBoxSize + colorBoxSpacing; double totalAdjustment = 0; @@ -6299,7 +6308,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { 0, lineCount - 1, ); - final buffer = 50; + const buffer = 50; final start = (firstVisible - buffer).clamp(0, lineCount - 1); final end = (lastVisible + buffer).clamp(0, lineCount - 1); @@ -6861,9 +6870,9 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { width: handleSize * 1.3, height: handleSize * 1.3, ), - topRight: Radius.circular(25), - bottomLeft: Radius.circular(25), - bottomRight: Radius.circular(25), + topRight: const Radius.circular(25), + bottomLeft: const Radius.circular(25), + bottomRight: const Radius.circular(25), ), handlePaint, ); @@ -6929,7 +6938,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { final rrect = RRect.fromRectAndRadius( Rect.fromLTWH(zoomBoxX, zoomBoxY, zoomBoxWidth, zoomBoxHeight), - Radius.circular(12), + const Radius.circular(12), ); canvas.drawRRect( @@ -6961,9 +6970,9 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { canvas.drawRRect( RRect.fromRectAndCorners( _startHandleRect!, - topLeft: Radius.circular(25), - bottomLeft: Radius.circular(25), - bottomRight: Radius.circular(25), + topLeft: const Radius.circular(25), + bottomLeft: const Radius.circular(25), + bottomRight: const Radius.circular(25), ), handlePaint, ); @@ -6973,9 +6982,9 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { canvas.drawRRect( RRect.fromRectAndCorners( _endHandleRect!, - topRight: Radius.circular(25), - bottomLeft: Radius.circular(25), - bottomRight: Radius.circular(25), + topRight: const Radius.circular(25), + bottomLeft: const Radius.circular(25), + bottomRight: const Radius.circular(25), ), handlePaint, ); @@ -7061,14 +7070,14 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { activeHandleRect = null; } - final fingerOffsetY = 60.0; + const fingerOffsetY = 60.0; final fingerOffsetX = _draggingStartHandle ? 30.0 : (_draggingEndHandle ? -30.0 : 0.0); - var zoomBoxX = (handleCenterX + fingerOffsetX - zoomBoxWidth / 2) + final zoomBoxX = (handleCenterX + fingerOffsetX - zoomBoxWidth / 2) .clamp(4.0, size.width - zoomBoxWidth - 4); var zoomBoxY = handleTopY - zoomBoxHeight - fingerOffsetY; - final viewportTop = 0.0; + const viewportTop = 0.0; final viewportBottom = size.height; if (zoomBoxY < viewportTop + 4) { @@ -7087,7 +7096,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { final rrect = RRect.fromRectAndRadius( Rect.fromLTWH(zoomBoxX, zoomBoxY, zoomBoxWidth, zoomBoxHeight), - Radius.circular(12), + const Radius.circular(12), ); canvas.drawRRect( @@ -7321,7 +7330,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { return false; } })) { - final icon = Icons.lightbulb_outline; + const icon = Icons.lightbulb_outline; final actionBulbPainter = TextPainter( text: TextSpan( text: String.fromCharCode(icon.codePoint), @@ -7505,10 +7514,10 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { ) { final viewTop = vscrollController.offset; final viewBottom = viewTop + vscrollController.position.viewportDimension; - final tabSize = 4; + const tabSize = 4; final cursorOffset = controller.selection.extentOffset; final currentLine = controller.getLineAtOffset(cursorOffset); - List<({int startLine, int endLine, int indentLevel, double guideX})> + final List<({int startLine, int endLine, int indentLevel, double guideX})> blocks = []; void processLine(int i) { @@ -7653,7 +7662,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { } } - final scanBackLimit = 500; + const scanBackLimit = 500; final scanStart = (firstVisibleLine - scanBackLimit).clamp( 0, firstVisibleLine, @@ -8049,8 +8058,8 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { final start = highlight.start; final end = highlight.end; - int startLine = controller.getLineAtOffset(start); - int endLine = controller.getLineAtOffset(end); + final int startLine = controller.getLineAtOffset(start); + final int endLine = controller.getLineAtOffset(end); if (endLine < firstVisibleLine || startLine > lastVisibleLine) continue; @@ -8221,8 +8230,8 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { ..color = selectionStyle.selectionColor ..style = PaintingStyle.fill; - int startLine = controller.getLineAtOffset(start); - int endLine = controller.getLineAtOffset(end); + final int startLine = controller.getLineAtOffset(start); + final int endLine = controller.getLineAtOffset(end); if (endLine < firstVisibleLine || startLine > lastVisibleLine) return; @@ -9743,7 +9752,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { final fontSize = textStyle?.fontSize ?? 14.0; final colorBoxSize = fontSize * 0.85; - final colorBoxSpacing = 4.0; + const colorBoxSpacing = 4.0; final totalColorWidth = colorBoxSize + colorBoxSpacing; final bgColor = editorTheme['root']?.backgroundColor ?? Colors.black; final colorsByLine = >{}; @@ -10066,7 +10075,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation { if ((hoverNotifier.value == null || !isHoveringPopup.value) && _isOffsetOverWord(textOffset)) { _hoverTimer?.cancel(); - _hoverTimer = Timer(Duration(milliseconds: 1500), () { + _hoverTimer = Timer(const Duration(milliseconds: 1500), () { final lineChar = _offsetToLineChar(textOffset); hoverNotifier.value = (event.localPosition, lineChar); }); diff --git a/lib/code_forge/controller.dart b/lib/code_forge/controller.dart index 3df1c7d..3118394 100644 --- a/lib/code_forge/controller.dart +++ b/lib/code_forge/controller.dart @@ -1,13 +1,13 @@ import 'dart:async'; import 'dart:io'; -import '../code_forge.dart'; -import 'rope.dart'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import '../code_forge.dart'; +import 'rope.dart'; + /// Controller for the [CodeForge] code editor widget. /// /// This controller manages the text content, selection state, and various @@ -49,7 +49,7 @@ class CodeForgeController implements DeltaTextInputClient { Timer? _lspTypingTimer; static const Duration _lspTypingDebounce = Duration(milliseconds: 300); String? _cachedText, _bufferLineText, _openedFile, _lastSentText; - String _previousValue = ""; + String _previousValue = ''; TextSelection _prevSelection = const TextSelection.collapsed(offset: 0); bool _bufferDirty = false, bufferNeedsRepaint = false, selectionOnly = false; int _bufferLineRopeStart = 0, _bufferLineOriginalLength = 0; @@ -149,16 +149,16 @@ class CodeForgeController implements DeltaTextInputClient { if (!_isDisposed) codeActionsNotifier.value = null; return; } - int minStartLine = errors + final int minStartLine = errors .map((d) => d.range['start']?['line'] as int? ?? 0) .reduce((a, b) => a < b ? a : b); - int minStartChar = errors + final int minStartChar = errors .map((d) => d.range['start']?['character'] as int? ?? 0) .reduce((a, b) => a < b ? a : b); - int maxEndLine = errors + final int maxEndLine = errors .map((d) => d.range['end']?['line'] as int? ?? 0) .reduce((a, b) => a > b ? a : b); - int maxEndChar = errors + final int maxEndChar = errors .map((d) => d.range['end']?['character'] as int? ?? 0) .reduce((a, b) => a > b ? a : b); @@ -1438,6 +1438,10 @@ class CodeForgeController implements DeltaTextInputClient { /// When true, the user cannot modify the text content. bool readOnly = false; + // custom tap size + int tapSize = 4; + String get tapSizeSpace => ' ' * tapSize; + /// Whether the line structure has changed (lines added or removed). bool lineStructureChanged = false; @@ -1461,7 +1465,7 @@ class CodeForgeController implements DeltaTextInputClient { void saveFile() { if (openedFile == null) { throw FlutterError( - "No file found.\nPlease open a file by providing a valid filePath to the CodeForge widget", + 'No file found.\nPlease open a file by providing a valid filePath to the CodeForge widget', ); } File(openedFile!).writeAsStringSync(text); @@ -1731,7 +1735,7 @@ class CodeForgeController implements DeltaTextInputClient { TextSelection(baseOffset: selection.baseOffset, extentOffset: 0), ); } else { - setSelectionSilently(TextSelection.collapsed(offset: 0)); + setSelectionSilently(const TextSelection.collapsed(offset: 0)); } } @@ -2937,7 +2941,7 @@ class CodeForgeController implements DeltaTextInputClient { final selectedBlock = text.substring(lineStart, lineEnd); final indentedBlock = selectedBlock .split('\n') - .map((line) => ' $line') + .map((line) => '$tapSizeSpace$line') .join('\n'); final lines = selectedBlock.split('\n'); @@ -2950,7 +2954,7 @@ class CodeForgeController implements DeltaTextInputClient { replaceRange(lineStart, lineEnd, indentedBlock); setSelectionSilently(newSelection); } else { - insertAtCurrentCursor(' '); + insertAtCurrentCursor(tapSizeSpace); } } @@ -2977,7 +2981,7 @@ class CodeForgeController implements DeltaTextInputClient { final unindentedBlock = selectedBlock .split('\n') .map( - (line) => line.startsWith(' ') + (line) => line.startsWith(tapSizeSpace) ? line.substring(3) : line.replaceFirst(RegExp(r'^ +'), ''), ) @@ -2996,7 +3000,7 @@ class CodeForgeController implements DeltaTextInputClient { final newSelection = TextSelection( baseOffset: selection.baseOffset - - (lines.first.startsWith(' ') + (lines.first.startsWith(tapSizeSpace) ? 3 : (RegExp(r'^ +').stringMatch(lines.first)?.length ?? 0)), extentOffset: selection.extentOffset - removedChars, diff --git a/lib/code_forge/scroll.dart b/lib/code_forge/scroll.dart index c9fe872..1c16bc9 100644 --- a/lib/code_forge/scroll.dart +++ b/lib/code_forge/scroll.dart @@ -73,7 +73,9 @@ class Render2DCodeField extends RenderTwoDimensionalViewport { @override void layoutChildSequence() { - final child = buildOrObtainChildFor(ChildVicinity(xIndex: 0, yIndex: 0)); + final child = buildOrObtainChildFor( + const ChildVicinity(xIndex: 0, yIndex: 0), + ); if (child != null) { child.layout( diff --git a/lib/code_forge/styling.dart b/lib/code_forge/styling.dart index c048ce0..76f8c66 100644 --- a/lib/code_forge/styling.dart +++ b/lib/code_forge/styling.dart @@ -907,8 +907,8 @@ class CustomTheme { 'meta': meta ?? TextStyle(color: meta?.color), 'selector-id': selectorId ?? TextStyle(color: selectorId?.color), 'title': title ?? TextStyle(color: title?.color), - 'emphasis': TextStyle(fontStyle: FontStyle.italic), - 'strong': TextStyle(fontWeight: FontWeight.bold), + 'emphasis': const TextStyle(fontStyle: FontStyle.italic), + 'strong': const TextStyle(fontWeight: FontWeight.bold), }; } } @@ -1005,7 +1005,7 @@ class CustomLanguageGrammar { scope: 'string', begin: "'", end: "'", - contains: [Mode(match: r"\\.")], + contains: [Mode(match: r'\\.')], ), ); contains.add( @@ -1013,7 +1013,7 @@ class CustomLanguageGrammar { scope: 'string', begin: '"', end: '"', - contains: [Mode(match: r"\\.")], + contains: [Mode(match: r'\\.')], ), ); } @@ -1437,7 +1437,7 @@ class CustomLanguageGrammar { } else if (regex[i] == ')') { depth--; if (depth == 0 && start >= 0 && !isNonCapturing) { - var content = regex.substring(start, i); + final content = regex.substring(start, i); groups.add(content); } } diff --git a/pubspec.yaml b/pubspec.yaml index 3cd0a34..6b90648 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: fonts: