Skip to content

Commit

Permalink
Refactor focusing to contextLine (apache#24613)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeyinkin committed Dec 15, 2022
1 parent aa6cc16 commit 401b098
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 229 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ class EmbeddedEditor extends StatelessWidget {
return const LoadingIndicator();
}

return SnippetEditor(
return EditorTextArea(
controller: snippetController,
isEditable: isEditable,
goToContextLine: false,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ class CodeTextAreaWrapper extends StatelessWidget {
child: Stack(
children: [
Positioned.fill(
child: SnippetEditor(
child: EditorTextArea(
controller: snippetController,
isEditable: true,
goToContextLine: true,
),
),
Positioned(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export 'src/widgets/clickable.dart';
export 'src/widgets/complexity.dart';
export 'src/widgets/dismissible_overlay.dart';
export 'src/widgets/divider.dart';
export 'src/widgets/editor_text_area.dart';
export 'src/widgets/header_icon_button.dart';
export 'src/widgets/loading_error.dart';
export 'src/widgets/loading_indicator.dart';
Expand All @@ -75,7 +76,6 @@ export 'src/widgets/reset_button.dart';
export 'src/widgets/run_or_cancel_button.dart';
export 'src/widgets/shortcut_tooltip.dart';
export 'src/widgets/shortcuts_manager.dart';
export 'src/widgets/snippet_editor.dart';
export 'src/widgets/split_view.dart';
export 'src/widgets/tab_header.dart';
export 'src/widgets/toasts/toast_listener.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -77,6 +79,7 @@ class SnippetEditingController extends ChangeNotifier {
codeController.removeListener(_onCodeControllerChanged);
setSource(example.source);
_applyViewOptions(viewOptions);
_toStartOfContextLineIfAny();
codeController.addListener(_onCodeControllerChanged);

notifyListeners();
Expand All @@ -100,6 +103,27 @@ class SnippetEditingController extends ChangeNotifier {
}
}

void _toStartOfContextLineIfAny() {
final contextLine1Based = selectedExample?.contextLine;

if (contextLine1Based == null) {
return;
}

_toStartOfFullLine(max(contextLine1Based - 1, 0));
}

void _toStartOfFullLine(int line) {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* 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 '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 '../theme/theme.dart';

class EditorTextArea extends StatefulWidget {
final SnippetEditingController controller;
final bool isEditable;

EditorTextArea({
required this.controller,
required this.isEditable,
}) : super(
// When the example is changed, will scroll to the context line again.
key: ValueKey(controller.selectedExample),
);

@override
State<EditorTextArea> createState() => _EditorTextAreaState();
}

class _EditorTextAreaState extends State<EditorTextArea> {
bool _didAutoFocus = false;
final _focusNode = FocusNode();
final _scrollController = ScrollController();
final _sizeKey = LabeledGlobalKey('CodeFieldKey');

@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() {
super.dispose();
_focusNode.dispose();
}

@override
Widget build(BuildContext context) {
final ext = Theme.of(context).extension<BeamThemeExtension>()!;
final isMultifile = widget.controller.selectedExample?.isMultiFile ?? false;
final isEnabled = widget.isEditable && !isMultifile;

return Semantics(
container: true,
textField: true,
multiline: true,
enabled: isEnabled,
readOnly: isEnabled,
label: 'widgets.codeEditor.label',
child: FocusScope(
key: _sizeKey,
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),
focusNode: _focusNode,
enabled: isEnabled,
controller: widget.controller.codeController,
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,
);
}
Loading

0 comments on commit 401b098

Please sign in to comment.