Skip to content

Commit b2594ea

Browse files
authored
restore cursor pos after formatting (#2885)
* restore cursor pos after formatting * remove a todo:
1 parent cab8af0 commit b2594ea

File tree

5 files changed

+72
-18
lines changed

5 files changed

+72
-18
lines changed

pkgs/sketch_pad/lib/editor/codemirror.dart

+15-2
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,15 @@ extension type CodeMirror._(JSObject _) implements JSObject {
4343
external void refresh();
4444
external void focus();
4545
external void showHint(HintOptions? options);
46-
external JSAny execCommand(String command, [JSAny? object]);
46+
external JSAny? execCommand(String command);
4747
external void on(String event, JSFunction callback);
4848

4949
String getTheme() => (getOption('theme') as JSString).toDart;
5050
void setTheme(String theme) => setOption('theme', theme.toJS);
5151

52+
external void scrollTo(num? x, num? y);
53+
external ScrollInfo getScrollInfo();
54+
5255
void setReadOnly(bool value, [bool noCursor = false]) {
5356
if (value) {
5457
if (noCursor) {
@@ -78,7 +81,7 @@ extension type Doc._(JSObject _) implements JSObject {
7881
external String getValue();
7982
external String? getLine(int n);
8083
external bool somethingSelected();
81-
external String? getSelection(String s);
84+
external String? getSelection(String? s);
8285
external void setSelection(Position position, [Position head]);
8386
external JSArray<TextMarker> getAllMarks();
8487
external TextMarker markText(
@@ -89,6 +92,16 @@ extension type Doc._(JSObject _) implements JSObject {
8992
external Position posFromIndex(int index);
9093
}
9194

95+
@anonymous
96+
extension type ScrollInfo._(JSObject _) implements JSObject {
97+
external int top;
98+
external int left;
99+
external int width;
100+
external int height;
101+
external int clientWidth;
102+
external int clientHeight;
103+
}
104+
92105
@anonymous
93106
extension type Position._(JSObject _) implements JSObject {
94107
external int line;

pkgs/sketch_pad/lib/editor/editor.dart

+36-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ import '../embed.dart';
1515
import '../model.dart';
1616
import 'codemirror.dart';
1717

18+
// TODO: show documentation on hover
19+
20+
// TODO: implement find / find next
21+
22+
// TODO: improve the code completion UI
23+
24+
// TODO: hover - show links to hosted dartdoc? (flutter, dart api, packages)
25+
1826
const String _viewType = 'dartpad-editor';
1927

2028
bool _viewFactoryInitialized = false;
@@ -120,6 +128,19 @@ class _EditorWidgetState extends State<EditorWidget> implements EditorService {
120128
codeMirror?.focus();
121129
}
122130

131+
@override
132+
int get cursorOffset {
133+
final pos = codeMirror?.getCursor();
134+
if (pos == null) return 0;
135+
136+
return codeMirror?.getDoc().indexFromPos(pos) ?? 0;
137+
}
138+
139+
@override
140+
void focus() {
141+
codeMirror?.focus();
142+
}
143+
123144
@override
124145
void initState() {
125146
super.initState();
@@ -231,8 +252,19 @@ class _EditorWidgetState extends State<EditorWidget> implements EditorService {
231252
}
232253

233254
void _updateCodemirrorFromModel() {
234-
final value = widget.appModel.sourceCodeController.text;
235-
codeMirror?.getDoc().setValue(value);
255+
final value = widget.appModel.sourceCodeController.value;
256+
final cursorOffset = value.selection.baseOffset;
257+
final cm = codeMirror!;
258+
final doc = cm.getDoc();
259+
260+
if (cursorOffset == -1) {
261+
doc.setValue(value.text);
262+
} else {
263+
final scrollInfo = cm.getScrollInfo();
264+
doc.setValue(value.text);
265+
doc.setSelection(doc.posFromIndex(cursorOffset));
266+
cm.scrollTo(scrollInfo.left, scrollInfo.top);
267+
}
236268
}
237269

238270
void _updateEditableStatus() {
@@ -324,10 +356,8 @@ class _EditorWidgetState extends State<EditorWidget> implements EditorService {
324356
// codemirror commands
325357

326358
JSAny? _handleGoLineLeft(CodeMirror editor) {
327-
// Change the cmd-left behavior to move the cursor to the leftmost non-ws
328-
// char.
329-
editor.execCommand('goLineLeftSmart');
330-
return JSObject();
359+
// Change the cmd-left behavior to move the cursor to leftmost non-ws char.
360+
return editor.execCommand('goLineLeftSmart');
331361
}
332362

333363
void _indentIfMultiLineSelectionElseInsertSoftTab(CodeMirror editor) {

pkgs/sketch_pad/lib/execution/frame_utils.dart

+7-6
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ import 'dart:js_interop_unsafe';
77

88
import 'package:web/web.dart';
99

10-
/// Extensions to work around Dart compiler issues that result
11-
/// in calls that sandboxed iframes error on.
10+
/// Extensions to work around Dart compiler issues that result in calls that
11+
/// sandboxed iframes error on.
12+
///
13+
/// If the compilers are adjusted to handle this case or `package:web` provides
14+
/// a helper for this, switch to that.
1215
///
13-
/// If the compilers are adjusted to handle this case or
14-
/// `package:web` provides a helper for this, switch to that.
1516
/// Tracked in https://github.com/dart-lang/sdk/issues/54443.
1617
extension HTMLIFrameElementExtension on HTMLIFrameElement {
17-
/// Send the specified [message] to this iframe,
18-
/// configured with the specified [optionsOrTargetOrigin].
18+
/// Send the specified [message] to this iframe, configured with the specified
19+
/// [optionsOrTargetOrigin].
1920
void safelyPostMessage(
2021
JSAny? message,
2122
String optionsOrTargetOrigin,

pkgs/sketch_pad/lib/main.dart

+12-4
Original file line numberDiff line numberDiff line change
@@ -470,15 +470,23 @@ class _DartPadMainPageState extends State<DartPadMainPage>
470470

471471
Future<void> _handleFormatting() async {
472472
try {
473-
final value = appModel.sourceCodeController.text;
474-
final result = await appServices.format(SourceRequest(source: value));
473+
final source = appModel.sourceCodeController.text;
474+
final offset = appServices.editorService?.cursorOffset;
475+
final result = await appServices.format(
476+
SourceRequest(source: source, offset: offset),
477+
);
475478

476-
if (result.source == value) {
479+
if (result.source == source) {
477480
appModel.editorStatus.showToast('No formatting changes');
478481
} else {
479482
appModel.editorStatus.showToast('Format successful');
480-
appModel.sourceCodeController.text = result.source;
483+
appModel.sourceCodeController.value = TextEditingValue(
484+
text: result.source,
485+
selection: TextSelection.collapsed(offset: result.offset ?? 0),
486+
);
481487
}
488+
489+
appServices.editorService!.focus();
482490
} catch (error) {
483491
appModel.editorStatus.showToast('Error formatting code');
484492
appModel.appendLineToConsole('Formatting issue: $error');

pkgs/sketch_pad/lib/model.dart

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ abstract class EditorService {
3333
void showCompletions();
3434
void showQuickFixes();
3535
void jumpTo(AnalysisIssue issue);
36+
int get cursorOffset;
37+
void focus();
3638
}
3739

3840
class AppModel {

0 commit comments

Comments
 (0)