You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by pa...@apache.org on 2022/12/21 20:06:25 UTC
[beam] branch master updated: Refactor focusing to contextLine (#24674)
This is an automated email from the ASF dual-hosted git repository.
pabloem pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 0917978677f Refactor focusing to contextLine (#24674)
0917978677f is described below
commit 0917978677f05385eb2ef0d89a016f4a2aa5c6c1
Author: alexeyinkin <al...@akvelon.com>
AuthorDate: Thu Dec 22 00:06:18 2022 +0400
Refactor focusing to contextLine (#24674)
* Refactor focusing to contextLine (#24613)
* Rename a widget (#24613)
* Minor reordering (#24613)
* Fix after review (#24613)
---
.../widgets/embedded_editor.dart | 1 -
.../widgets/editor_textarea_wrapper.dart | 1 -
.../controllers/snippet_editing_controller.dart | 28 ++++
.../lib/src/models/example_base.dart | 2 +
.../lib/src/widgets/editor_textarea.dart | 178 ---------------------
.../lib/src/widgets/snippet_editor.dart | 117 ++++++++++++--
6 files changed, 133 insertions(+), 194 deletions(-)
diff --git a/playground/frontend/lib/pages/embedded_playground/widgets/embedded_editor.dart b/playground/frontend/lib/pages/embedded_playground/widgets/embedded_editor.dart
index 94000034f29..ac319426c79 100644
--- a/playground/frontend/lib/pages/embedded_playground/widgets/embedded_editor.dart
+++ b/playground/frontend/lib/pages/embedded_playground/widgets/embedded_editor.dart
@@ -37,7 +37,6 @@ class EmbeddedEditor extends StatelessWidget {
return SnippetEditor(
controller: snippetController,
isEditable: isEditable,
- goToContextLine: false,
);
}
}
diff --git a/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart b/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
index 65c6e4177c6..57c1ba3fa70 100644
--- a/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
+++ b/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
@@ -56,7 +56,6 @@ class CodeTextAreaWrapper extends StatelessWidget {
child: SnippetEditor(
controller: snippetController,
isEditable: true,
- goToContextLine: true,
),
),
Positioned(
diff --git a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
index 195c0bd1e10..5963837f8c5 100644
--- a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
+++ b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
@@ -16,6 +16,8 @@
* limitations under the License.
*/
+import 'dart:math';
+
import 'package:flutter/widgets.dart';
import 'package:flutter_code_editor/flutter_code_editor.dart';
import 'package:get_it/get_it.dart';
@@ -77,6 +79,7 @@ class SnippetEditingController extends ChangeNotifier {
codeController.removeListener(_onCodeControllerChanged);
setSource(example.source);
_applyViewOptions(viewOptions);
+ _toStartOfContextLineIfAny();
codeController.addListener(_onCodeControllerChanged);
notifyListeners();
@@ -100,6 +103,31 @@ class SnippetEditingController extends ChangeNotifier {
}
}
+ void _toStartOfContextLineIfAny() {
+ final contextLine1Based = selectedExample?.contextLine;
+
+ if (contextLine1Based == null) {
+ return;
+ }
+
+ _toStartOfFullLine(max(contextLine1Based - 1, 0));
+ }
+
+ void _toStartOfFullLine(int line) {
+ if (line >= codeController.code.lines.length) {
+ return;
+ }
+
+ final fullPosition = codeController.code.lines.lines[line].textRange.start;
+ final visiblePosition = codeController.code.hiddenRanges.cutPosition(
+ fullPosition,
+ );
+
+ codeController.selection = TextSelection.collapsed(
+ offset: visiblePosition,
+ );
+ }
+
Example? get selectedExample => _selectedExample;
ExampleLoadingDescriptor? get descriptor => _descriptor;
diff --git a/playground/frontend/playground_components/lib/src/models/example_base.dart b/playground/frontend/playground_components/lib/src/models/example_base.dart
index dd9b7fcb34c..2ca76d107b4 100644
--- a/playground/frontend/playground_components/lib/src/models/example_base.dart
+++ b/playground/frontend/playground_components/lib/src/models/example_base.dart
@@ -50,6 +50,8 @@ extension ExampleTypeToString on ExampleType {
/// These objects are fetched as lists from [ExampleRepository].
class ExampleBase with Comparable<ExampleBase>, EquatableMixin {
final Complexity? complexity;
+
+ /// Index of the line to focus, 1-based.
final int contextLine;
final String description;
final bool isMultiFile;
diff --git a/playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart b/playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart
deleted file mode 100644
index 571415a1e71..00000000000
--- a/playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// TODO(alexeyinkin): Refactor this, merge into snippet_editor.dart
-
-import 'package:flutter/material.dart';
-import 'package:flutter_code_editor/flutter_code_editor.dart';
-
-import '../models/example.dart';
-import '../models/sdk.dart';
-import '../theme/theme.dart';
-
-const kJavaRegExp = r'import\s[A-z.0-9]*\;\n\n[(\/\*\*)|(public)|(class)]';
-const kPythonRegExp = r'[^\S\r\n](import|as)[^\S\r\n][A-z]*\n\n';
-const kGoRegExp = r'[^\S\r\n]+\'
- r'"'
- r'.*'
- r'"'
- r'\n\)\n\n';
-const kAdditionalLinesForScrolling = 4;
-
-class EditorTextArea extends StatefulWidget {
- final CodeController codeController;
- final Sdk sdk;
- final Example? example;
- final bool enabled;
- final bool isEditable;
- final bool goToContextLine;
-
- const EditorTextArea({
- super.key,
- required this.codeController,
- required this.sdk,
- this.example,
- required this.enabled,
- required this.isEditable,
- required this.goToContextLine,
- });
-
- @override
- State<EditorTextArea> createState() => _EditorTextAreaState();
-}
-
-class _EditorTextAreaState extends State<EditorTextArea> {
- var focusNode = FocusNode();
- final GlobalKey _sizeKey = LabeledGlobalKey('CodeFieldKey');
-
- @override
- void dispose() {
- super.dispose();
- focusNode.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- if (widget.goToContextLine) {
- WidgetsBinding.instance.addPostFrameCallback((_) => _setTextScrolling());
- }
-
- final ext = Theme.of(context).extension<BeamThemeExtension>()!;
-
- return Semantics(
- container: true,
- textField: true,
- multiline: true,
- enabled: widget.enabled,
- readOnly: widget.enabled,
- label: 'widgets.codeEditor.label',
- child: FocusScope(
- key: _sizeKey,
- node: FocusScopeNode(canRequestFocus: widget.isEditable),
- child: CodeTheme(
- data: ext.codeTheme,
- child: Container(
- color: ext.codeTheme.styles['root']?.backgroundColor,
- child: SingleChildScrollView(
- child: CodeField(
- key: ValueKey(widget.codeController),
- focusNode: focusNode,
- enabled: widget.enabled,
- controller: widget.codeController,
- textStyle: ext.codeRootStyle,
- ),
- ),
- ),
- ),
- ),
- );
- }
-
- void _setTextScrolling() {
- focusNode.requestFocus();
- if (widget.codeController.text.isNotEmpty) {
- widget.codeController.selection = TextSelection.fromPosition(
- TextPosition(
- offset: _getOffset(),
- ),
- );
- }
- }
-
- int _getOffset() {
- int contextLine = _getIndexOfContextLine();
- String pattern = _getPattern(_getQntOfStringsOnScreen());
- if (pattern == '' || pattern == '}') {
- return widget.codeController.text.lastIndexOf(pattern);
- }
-
- return widget.codeController.text.indexOf(
- pattern,
- contextLine,
- );
- }
-
- String _getPattern(int qntOfStrings) {
- int contextLineIndex = _getIndexOfContextLine();
- List<String> stringsAfterContextLine =
- widget.codeController.text.substring(contextLineIndex).split('\n');
-
- String result =
- stringsAfterContextLine.length + kAdditionalLinesForScrolling >
- qntOfStrings
- ? _getResultSubstring(stringsAfterContextLine, qntOfStrings)
- : stringsAfterContextLine.last;
-
- return result;
- }
-
- int _getQntOfStringsOnScreen() {
- final renderBox = _sizeKey.currentContext!.findRenderObject()! as RenderBox;
- final height = renderBox.size.height * .75;
-
- return height ~/ codeFontSize;
- }
-
- int _getIndexOfContextLine() {
- final contextLine = widget.example!.contextLine;
- final code = widget.codeController.code;
- final fullCharIndex = code.lines.lines[contextLine].textRange.start;
- final visibleCharIndex = code.hiddenRanges.cutPosition(fullCharIndex);
-
- return visibleCharIndex;
- }
-
- // This function made for more accuracy in the process of finding an exact line.
- String _getResultSubstring(
- List<String> stringsAfterContextLine,
- int qntOfStrings,
- ) {
- StringBuffer result = StringBuffer();
-
- for (int i = qntOfStrings - kAdditionalLinesForScrolling;
- i < qntOfStrings + kAdditionalLinesForScrolling;
- i++) {
- if (i == stringsAfterContextLine.length - 1) {
- return result.toString();
- }
- result.write(stringsAfterContextLine[i] + '\n');
- }
-
- return result.toString();
- }
-}
diff --git a/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart b/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
index fe7ecc4e603..ddcba180b89 100644
--- a/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
@@ -16,31 +16,120 @@
* limitations under the License.
*/
-import 'package:flutter/widgets.dart';
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter_code_editor/flutter_code_editor.dart';
import '../controllers/snippet_editing_controller.dart';
-import 'editor_textarea.dart';
+import '../theme/theme.dart';
-class SnippetEditor extends StatelessWidget {
+class SnippetEditor extends StatefulWidget {
final SnippetEditingController controller;
final bool isEditable;
- final bool goToContextLine;
- const SnippetEditor({
+ SnippetEditor({
required this.controller,
required this.isEditable,
- required this.goToContextLine,
- });
+ }) : super(
+ // When the example is changed, will scroll to the context line again.
+ key: ValueKey(controller.selectedExample),
+ );
+
+ @override
+ State<SnippetEditor> createState() => _SnippetEditorState();
+}
+
+class _SnippetEditorState extends State<SnippetEditor> {
+ bool _didAutoFocus = false;
+ final _focusNode = FocusNode();
+ final _scrollController = ScrollController();
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+
+ if (!_didAutoFocus) {
+ _didAutoFocus = true;
+ SchedulerBinding.instance.addPostFrameCallback((_) {
+ if (mounted) {
+ _scrollSoCursorIsOnTop();
+ }
+ });
+ }
+ }
+
+ void _scrollSoCursorIsOnTop() {
+ _focusNode.requestFocus();
+
+ final position = max(widget.controller.codeController.selection.start, 0);
+ final characterOffset = _getLastCharacterOffset(
+ text: widget.controller.codeController.text.substring(0, position),
+ style: kLightTheme.extension<BeamThemeExtension>()!.codeRootStyle,
+ );
+
+ _scrollController.jumpTo(
+ min(
+ characterOffset.dy,
+ _scrollController.position.maxScrollExtent,
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ _focusNode.dispose();
+ super.dispose();
+ }
@override
Widget build(BuildContext context) {
- return EditorTextArea(
- codeController: controller.codeController,
- sdk: controller.sdk,
- enabled: !(controller.selectedExample?.isMultiFile ?? false),
- example: controller.selectedExample,
- isEditable: isEditable,
- goToContextLine: goToContextLine,
+ final ext = Theme.of(context).extension<BeamThemeExtension>()!;
+ final isMultiFile = widget.controller.selectedExample?.isMultiFile ?? false;
+ final isEnabled = widget.isEditable && !isMultiFile;
+
+ return Semantics(
+ container: true,
+ enabled: isEnabled,
+ label: 'widgets.codeEditor.label',
+ multiline: true,
+ readOnly: isEnabled,
+ textField: true,
+ child: FocusScope(
+ node: FocusScopeNode(canRequestFocus: isEnabled),
+ child: CodeTheme(
+ data: ext.codeTheme,
+ child: Container(
+ color: ext.codeTheme.styles['root']?.backgroundColor,
+ child: SingleChildScrollView(
+ controller: _scrollController,
+ child: CodeField(
+ key: ValueKey(widget.controller.codeController),
+ controller: widget.controller.codeController,
+ enabled: isEnabled,
+ focusNode: _focusNode,
+ textStyle: ext.codeRootStyle,
+ ),
+ ),
+ ),
+ ),
+ ),
);
}
}
+
+Offset _getLastCharacterOffset({
+ required String text,
+ required TextStyle style,
+}) {
+ final textPainter = TextPainter(
+ textDirection: TextDirection.ltr,
+ text: TextSpan(text: text, style: style),
+ )..layout();
+
+ return textPainter.getOffsetForCaret(
+ TextPosition(offset: text.length),
+ Rect.zero,
+ );
+}