Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 38 additions & 13 deletions example/lib/ide/editor/code_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,33 @@ class CodeLines extends StatefulWidget {
}

class _CodeLinesState extends State<CodeLines> implements CodeLinesLayout {
final _lineKeys = <int, GlobalKey>{};
final _lineKeys = <GlobalKey>[];

@override
void initState() {
super.initState();

for (int i = 0; i < widget.codeLines.length; i++) {
_lineKeys.add(GlobalKey(debugLabel: "Code line: $i"));
}
}

@override
void didUpdateWidget(covariant CodeLines oldWidget) {
super.didUpdateWidget(oldWidget);

// Update the line keys to match the new code lines length, if needed.
if (widget.codeLines.length > oldWidget.codeLines.length) {
// Add more lines until we reach the new code lines length.
while (_lineKeys.length < widget.codeLines.length) {
final lineIndex = _lineKeys.length;
_lineKeys.add(GlobalKey(debugLabel: "Code line: $lineIndex"));
}
} else if (widget.codeLines.length < oldWidget.codeLines.length) {
// Remove the exceding lines.
_lineKeys.removeRange(widget.codeLines.length, oldWidget.codeLines.length);
}
}

@override
CodeRange? findWordBoundaryAtGlobalOffset(Offset globalOffset) {
Expand All @@ -54,8 +80,8 @@ class _CodeLinesState extends State<CodeLines> implements CodeLinesLayout {

@override
CodePosition findCodePositionNearestGlobalOffset(Offset globalOffset) {
for (int lineIndex in _lineKeys.keys) {
final lineLayout = _lineKeys[lineIndex]!.asCodeLine;
for (final lineKey in _lineKeys) {
final lineLayout = lineKey.asCodeLine;
if (!lineLayout.containsGlobalYValue(globalOffset.dy)) {
continue;
}
Expand All @@ -77,7 +103,7 @@ class _CodeLinesState extends State<CodeLines> implements CodeLinesLayout {
final boxes = <TextBox>[];

// Add the boxes for the first selected line, which may start at the middle of the line.
final firstCodeLine = _lineKeys[codeRange.start.line]!.asCodeLine;
final firstCodeLine = _lineKeys[codeRange.start.line].asCodeLine;
final firstCodeLineBoxes = firstCodeLine.getBoxesForSelection(
TextSelection(
baseOffset: codeRange.start.characterOffset,
Expand All @@ -90,7 +116,7 @@ class _CodeLinesState extends State<CodeLines> implements CodeLinesLayout {

// Add the boxes for the lines between the first and the last, which are fully selected.
for (int lineIndex = codeRange.start.line + 1; lineIndex < codeRange.end.line - 1; lineIndex += 1) {
final codeLine = _lineKeys[lineIndex]!.asCodeLine;
final codeLine = _lineKeys[lineIndex].asCodeLine;
final codeLineBoxes = codeLine.getBoxesForSelection(
TextSelection(
baseOffset: 0,
Expand All @@ -102,7 +128,7 @@ class _CodeLinesState extends State<CodeLines> implements CodeLinesLayout {

// Add the boxes for the last selected line, which may end at the middle of the line.
if (codeRange.start.line != codeRange.end.line) {
final lastCodeLine = _lineKeys[codeRange.end.line]!.asCodeLine;
final lastCodeLine = _lineKeys[codeRange.end.line].asCodeLine;
final lastCodeLineBoxes = lastCodeLine.getBoxesForSelection(
TextSelection(
baseOffset: 0,
Expand All @@ -117,7 +143,7 @@ class _CodeLinesState extends State<CodeLines> implements CodeLinesLayout {

List<TextBox> _mapCodeLineTextBoxesToLayoutTextBoxes(int lineIndex, List<TextBox> boxes) {
final layoutRenderBox = context.findRenderObject() as RenderBox;
final codeLineRenderBox = _lineKeys[lineIndex]!.currentContext!.findRenderObject() as RenderBox;
final codeLineRenderBox = _lineKeys[lineIndex].currentContext!.findRenderObject() as RenderBox;

return boxes.map(
(textBox) {
Expand Down Expand Up @@ -149,13 +175,13 @@ class _CodeLinesState extends State<CodeLines> implements CodeLinesLayout {
return _lineKeys[0]!.asCodeLine;
}
if (globalOffset.dy > linesTopLeft.dy + linesBox.size.height) {
return _lineKeys[_lineKeys.length]!.asCodeLine;
return _lineKeys[_lineKeys.length].asCodeLine;
}

// The offset is somewhere within the lines. Find the line that
// contains the offset.
for (int lineIndex in _lineKeys.keys) {
final lineLayout = _lineKeys[lineIndex]!.asCodeLine;
for (final lineKey in _lineKeys) {
final lineLayout = lineKey.asCodeLine;
if (!lineLayout.containsGlobalYValue(globalOffset.dy)) {
continue;
}
Expand All @@ -167,8 +193,8 @@ class _CodeLinesState extends State<CodeLines> implements CodeLinesLayout {
}

CodeLineLayout? _findLineLayoutAtGlobalOffset(Offset globalOffset) {
for (int lineIndex in _lineKeys.keys) {
final lineLayout = _lineKeys[lineIndex]!.asCodeLine;
for (final lineKey in _lineKeys) {
final lineLayout = lineKey.asCodeLine;
if (!lineLayout.containsGlobalYValue(globalOffset.dy)) {
continue;
}
Expand Down Expand Up @@ -218,7 +244,6 @@ class _CodeLinesState extends State<CodeLines> implements CodeLinesLayout {
}

Widget _buildCodeLine(int lineIndex) {
_lineKeys[lineIndex] ??= GlobalKey(debugLabel: "Code line: $lineIndex");
final key = _lineKeys[lineIndex];

return CodeLine(
Expand Down
77 changes: 20 additions & 57 deletions example/lib/ide/editor/editor.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';

import 'package:example/ide/editor/code_layout.dart';
import 'package:example/ide/editor/syntax_highlighting.dart';
import 'package:example/ide/ide_controller.dart';
import 'package:example/ide/infrastructure/keyboard_shortcuts.dart';
import 'package:example/ide/infrastructure/popover_list.dart';
import 'package:example/ide/theme.dart';
import 'package:example/lsp_exploration/lsp/lsp_client.dart';
import 'package:example/lsp_exploration/lsp/messages/code_actions.dart';
import 'package:example/lsp_exploration/lsp/messages/common_types.dart';
import 'package:example/lsp_exploration/lsp/messages/go_to_definition.dart';
import 'package:example/lsp_exploration/lsp/messages/hover.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:follow_the_leader/follow_the_leader.dart';
import 'package:path/path.dart' as path;
import 'package:super_editor/super_editor.dart';
import 'package:super_editor_markdown/super_editor_markdown.dart';
import 'package:syntax_highlight/syntax_highlight.dart';
Expand All @@ -29,14 +27,18 @@ class IdeEditor extends StatefulWidget {
});

final LspClient lspClient;
final File? sourceFile;
final IdeFile sourceFile;
final void Function(String uri, Range range)? onGoToDefinition;

@override
State<IdeEditor> createState() => _IdeEditorState();
}

class _IdeEditorState extends State<IdeEditor> {
class _IdeEditorState extends State<IdeEditor> with AutomaticKeepAliveClientMixin {
/// Keeps the state when changing between tabs.
@override
bool get wantKeepAlive => true;

final _linesKey = GlobalKey();
late FollowerBoundary _screenBoundary;

Expand Down Expand Up @@ -67,8 +69,6 @@ class _IdeEditorState extends State<IdeEditor> {

final _focusNode = FocusNode();

final _fileContent = ValueNotifier<String>("");

var _styledLines = <TextSpan>[];

Position? _currentSelectedPosition;
Expand All @@ -77,12 +77,7 @@ class _IdeEditorState extends State<IdeEditor> {
void initState() {
super.initState();

if (widget.sourceFile != null) {
_fileContent.value = widget.sourceFile!.readAsStringSync();
}
_initializeSyntaxHighlighting();

_fileContent.addListener(_onFileContentChange);
}

@override
Expand All @@ -98,16 +93,14 @@ class _IdeEditorState extends State<IdeEditor> {
@override
void didUpdateWidget(IdeEditor oldWidget) {
super.didUpdateWidget(oldWidget);

if (widget.sourceFile != oldWidget.sourceFile) {
_fileContent.value = widget.sourceFile!.readAsStringSync();
if (oldWidget.sourceFile != widget.sourceFile) {
_highlightSyntax();
}
}

@override
void dispose() {
_hoverTimer?.cancel();
_fileContent.dispose();
_focusNode.dispose();
super.dispose();
}
Expand All @@ -121,21 +114,23 @@ class _IdeEditorState extends State<IdeEditor> {
language: 'dart',
theme: theme,
);
}

void _onFileContentChange() {
if (!mounted) {
return;
}

_highlightSyntax();
}

void _highlightSyntax() {
setState(() {
_styledLines = highlightSyntaxByLine(_highlighter!, _fileContent.value);
_styledLines = highlightSyntaxByLine(_highlighter!, widget.sourceFile.content);

print("Displaying ${_styledLines.length} styled lines");
// print("Displaying ${_styledLines.length} styled lines");
for (final span in _styledLines) {
final buffer = StringBuffer();
span.computeToPlainText(buffer);
print("Line: '${buffer.toString()}'");
// print("Line: '${buffer.toString()}'");
}
});
}
Expand All @@ -151,22 +146,14 @@ class _IdeEditorState extends State<IdeEditor> {
}

final sourceFile = widget.sourceFile;
if (sourceFile == null) {
return;
}

if (_shouldAbortCurrentHoverRequest(position)) {
return;
}

final filePath = sourceFile.isAbsolute //
? sourceFile.path
: path.absolute(sourceFile.path);

final res = await widget.lspClient.hover(
HoverParams(
textDocument: TextDocumentIdentifier(
uri: "file://$filePath",
uri: sourceFile.uri,
),
position: Position(
line: position.line,
Expand Down Expand Up @@ -208,13 +195,7 @@ class _IdeEditorState extends State<IdeEditor> {
return;
}

final openedFile = widget.sourceFile;
if (openedFile == null) {
_hoverOverlayController.hide();
return;
}

if (_fileContent.value.isEmpty) {
if (widget.sourceFile.content.isEmpty) {
_hoverOverlayController.hide();
return;
}
Expand Down Expand Up @@ -253,11 +234,6 @@ class _IdeEditorState extends State<IdeEditor> {
return true;
}

if (widget.sourceFile == null) {
// There isn't any opened file to hover on.
return true;
}

if (hoveredCodePosition != _latestHoveredCodePosition) {
// The user hovered another position while the hover request was happening. Ignore the results,
// because a new request will happen.
Expand Down Expand Up @@ -285,11 +261,6 @@ class _IdeEditorState extends State<IdeEditor> {
_hoverOverlayController.hide();
}

final sourceFile = widget.sourceFile;
if (sourceFile == null) {
return;
}

final codeLines = _linesKey.asCodeLines;
final codePosition = codeLines.findCodePositionNearestGlobalOffset(details.globalPosition);

Expand Down Expand Up @@ -333,23 +304,14 @@ class _IdeEditorState extends State<IdeEditor> {
_hoverOverlayController.hide();
}

final sourceFile = widget.sourceFile;
if (sourceFile == null) {
return;
}

final position = _currentSelectedPosition;
if (position == null) {
return;
}

final filePath = sourceFile.isAbsolute //
? sourceFile.path
: path.absolute(sourceFile.path);

final res = await widget.lspClient.codeAction(
CodeActionsParams(
textDocument: TextDocumentIdentifier(uri: 'file://$filePath'),
textDocument: TextDocumentIdentifier(uri: widget.sourceFile.uri),
range: Range(start: position, end: position),
context: const CodeActionContext(
triggerKind: CodeActionTriggerKind.invoked,
Expand All @@ -374,6 +336,7 @@ class _IdeEditorState extends State<IdeEditor> {

@override
Widget build(BuildContext context) {
super.build(context);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's part of the AutomaticKeepAliveClientMixin, to keep the state while changing between tabs.

return Actions(
actions: {
IncreaseFontSizeIntent: CallbackAction<IncreaseFontSizeIntent>(
Expand Down
22 changes: 11 additions & 11 deletions example/lib/ide/editor/syntax_highlighting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ List<TextSpan> _breakUpTextSpanTreeIntoLines(
styledLines.add(currentLine);
int currentOffset = 0;

print("----------- PROCESSING SPANS ------------");
// print("----------- PROCESSING SPANS ------------");
while (spanStack.isNotEmpty) {
print("----------------");
// print("----------------");
final span = spanStack.removeAt(0);
print("Span:\n'${span.text}'");
print("-----");
// print("Span:\n'${span.text}'");
// print("-----");

if (span.children != null) {
// Push the children of this span onto the span stack so that they'll be
Expand All @@ -38,19 +38,19 @@ List<TextSpan> _breakUpTextSpanTreeIntoLines(

if (span.text == null) {
// There's no text in this span. Move on to the children, or later spans.
print("The span has no text. Moving to next span.");
// print("The span has no text. Moving to next span.");
continue;
}

span.computeToPlainText(debugBuffer);
debugBuffer.writeln();

final endOfSpan = currentOffset + span.text!.length;
print("Start of this span: $currentOffset");
print("End of this span: $endOfSpan");
// print("Start of this span: $currentOffset");
// print("End of this span: $endOfSpan");

if (!span.text!.contains("\n")) {
print("This span has no newlines, appending to ongoing line.");
// print("This span has no newlines, appending to ongoing line.");
// All the content in this span belongs to the current line. Append it.
currentLine.children!.add(span);
// print(
Expand All @@ -60,7 +60,7 @@ List<TextSpan> _breakUpTextSpanTreeIntoLines(
continue;
}

print("This span contains multiple lines of text...");
// print("This span contains multiple lines of text...");
final lines = span.text!.split("\n");
for (int i = 0; i < lines.length; i += 1) {
final line = lines[i];
Expand All @@ -75,10 +75,10 @@ List<TextSpan> _breakUpTextSpanTreeIntoLines(
// Write the current line to debug output for verification.
final buffer = StringBuffer();
currentLine.computeToPlainText(buffer);
print("COMMITTING LINE: '${buffer.toString()}'");
// print("COMMITTING LINE: '${buffer.toString()}'");

// Create a new line to append remaining text.
print("STARTING NEW (BLANK) CODE LINE");
// print("STARTING NEW (BLANK) CODE LINE");
currentLine = TextSpan(text: "", children: []);
styledLines.add(currentLine);
}
Expand Down
Loading