Skip to content
Merged
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
75 changes: 63 additions & 12 deletions example/lib/ide/file_explorer/file_explorer.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import 'dart:io';
import 'dart:math';

import 'package:example/ide/ide.dart';
import 'package:example/ide/infrastructure/user_settings.dart';
import 'package:example/lsp_exploration/lsp/lsp_client.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart';
import 'package:path/path.dart' as path;

import '../../lsp_exploration/lsp/messages/rename_files_params.dart';

// TODO:
// - switch from single, double tap recognizers to multi-tap to get rid of selection delay
Expand All @@ -30,9 +37,11 @@ class FileExplorer extends StatefulWidget {
super.key,
required this.directory,
required this.onFileOpenRequested,
required this.lspClient,
});

final Directory directory;
final LspClient lspClient;

final void Function(File) onFileOpenRequested;

Expand All @@ -51,6 +60,8 @@ class _FileExplorerState extends State<FileExplorer> {
late final List<FileSystemEntityTreeViewNode> _tree;
FileSystemEntityTreeViewNode? _selectedNode;

final FocusNode _treeRowFocusNode = FocusNode();

@override
void initState() {
super.initState();
Expand Down Expand Up @@ -93,6 +104,36 @@ class _FileExplorerState extends State<FileExplorer> {
}
}

Future<void> _renameFile(BuildContext context, String currentFilePath) async {
final String? newFileName = await showDialog<String>(
context: context,
builder: (context) {
return const RenameFileDialog();
});

if (newFileName == null) {
return;
}

final newFilePath = path.join(path.dirname(currentFilePath), "$newFileName${path.extension(currentFilePath)}");

final params = RenameFilesParams(
files: [
FileRename(
oldUri: "file://$currentFilePath",
newUri: "file://$newFilePath",
// TODO: Create a object to serialize the file path as understood by the LSP. Eg. file://$root/example/lib/ide/idea.dart
),
],
);

await widget.lspClient.willRenameFiles(params);

// TODO: modify names, apply changes.

await widget.lspClient.didRenameFiles(params);
}

static const _rowHeight = 28.0;

@override
Expand Down Expand Up @@ -201,17 +242,27 @@ class _FileExplorerState extends State<FileExplorer> {
const SizedBox(width: 4.0),
// Content
Center(
child: Row(
children: [
_getIconForNode(node),
const SizedBox(width: 8),
Text(
node.title,
style: const TextStyle(
fontSize: 14,
child: KeyboardListener(
focusNode: _treeRowFocusNode,
onKeyEvent: (value) {
if (value.logicalKey == LogicalKeyboardKey.enter) {
if (node.isFile) {
_renameFile(context, _selectedNode!.asFile.path);
}
}
},
child: Row(
children: [
_getIconForNode(node),
const SizedBox(width: 8),
Text(
node.title,
style: const TextStyle(
fontSize: 14,
),
),
),
],
],
),
),
),
],
Expand Down Expand Up @@ -249,13 +300,13 @@ class _FileExplorerState extends State<FileExplorer> {
// Open document.
setState(() {
_selectedNode = node;
_treeRowFocusNode.requestFocus();
});

if(node.isFile){
if (node.isFile) {
// Open document.
widget.onFileOpenRequested(node.asFile);
}

},
),
DoubleTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
Expand Down
93 changes: 68 additions & 25 deletions example/lib/ide/ide.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import 'package:example/lsp_exploration/lsp/messages/rename_files_params.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import 'package:path/path.dart' as path;

class IDE extends StatefulWidget {
const IDE({
super.key,
Expand All @@ -32,6 +34,8 @@ class _IDEState extends State<IDE> {
bool _showLeftPane = true;
bool _isAnalyzing = false;

String? currentFile;

@override
void initState() {
super.initState();
Expand Down Expand Up @@ -110,6 +114,11 @@ class _IDEState extends State<IDE> {
workspace: widget.workspace,
showLeftPane: _showLeftPane,
showRightPane: true,
onFileOpen: (value) {
setState(() {
currentFile = value;
});
},
),
),
const RightBar(),
Expand All @@ -121,6 +130,7 @@ class _IDEState extends State<IDE> {
onLspRestartPressed: _restartLspClient,
onLspStopPressed: _stopLspClient,
isAnalyzing: _isAnalyzing,
currentFilePath: currentFile,
),
],
),
Expand Down Expand Up @@ -235,28 +245,14 @@ class BottomBar extends StatelessWidget {
required this.onLspRestartPressed,
required this.onLspStopPressed,
required this.isAnalyzing,
this.currentFilePath,
});

final LspClient lspClient;
final VoidCallback onLspRestartPressed;
final VoidCallback onLspStopPressed;
final bool isAnalyzing;

Future<void> _renameFile() async {
print("Rename file");
final root = UserSettings().contentDirectory;
print('root: $root');
final params = RenameFilesParams(
files: [
FileRename(
oldUri: "file://$root/example/lib/ide/ide.dart",
newUri: "file://$root/example/lib/ide/idea.dart",
),
],
);
final data = await lspClient.willRenameFiles(params);
print("Rename file data: $data");
}
final String? currentFilePath;

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -314,13 +310,10 @@ class BottomBar extends StatelessWidget {
Text("LSP: $_lspClientStatus"),
const SizedBox(width: 8),
// This is here to experiment with the LspClient
GestureDetector(
onTap: _renameFile,
child: const Icon(
Icons.science_outlined,
size: 14,
color: Colors.lightBlueAccent,
),
const Icon(
Icons.science_outlined,
size: 14,
color: Colors.lightBlueAccent,
),
const SizedBox(width: 10),
GestureDetector(
Expand Down Expand Up @@ -373,18 +366,65 @@ class BottomBar extends StatelessWidget {
}
}

class RenameFileDialog extends StatefulWidget {
const RenameFileDialog({
super.key,
});

@override
State<RenameFileDialog> createState() => _RenameFileDialogState();
}

class _RenameFileDialogState extends State<RenameFileDialog> {
final TextEditingController textEditingController = TextEditingController();

@override
Widget build(BuildContext context) {
return SimpleDialog(
title: const Text("Rename file"),
children: [
TextField(
controller: textEditingController,
decoration: const InputDecoration(
labelText: "New file name",
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Cancel"),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(textEditingController.text);
},
child: const Text("Rename"),
),
],
),
],
);
}
}

class ContentArea extends StatefulWidget {
const ContentArea({
super.key,
required this.workspace,
required this.showLeftPane,
required this.showRightPane,
required this.onFileOpen,
});

final Workspace workspace;

final bool showLeftPane;
final bool showRightPane;
final Function(String documentUrl) onFileOpen;

@override
State<ContentArea> createState() => _ContentAreaState();
Expand Down Expand Up @@ -425,8 +465,9 @@ class _ContentAreaState extends State<ContentArea> {
),
),
child: FileExplorer(
lspClient: widget.workspace.lspClient,
directory: widget.workspace.directory,
onFileOpenRequested: (file) {
onFileOpenRequested: (file) async {
_fileContent.value = file.readAsStringSync();
_currentFile.value = file;

Expand All @@ -437,7 +478,7 @@ class _ContentAreaState extends State<ContentArea> {
languageId = "yaml";
}

widget.workspace.lspClient.didOpenTextDocument(
await widget.workspace.lspClient.didOpenTextDocument(
DidOpenTextDocumentParams(
textDocument: TextDocumentItem(
uri: "file://${file.absolute.path}",
Expand All @@ -447,6 +488,8 @@ class _ContentAreaState extends State<ContentArea> {
),
),
);

widget.onFileOpen(_currentFile.value!.path);
},
),
),
Expand Down
9 changes: 9 additions & 0 deletions example/lib/lsp_exploration/lsp/lsp_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ class LspClient with ChangeNotifier {

return data.value.map((e) => LspCodeAction.fromJson(e)).toList();
}

Future<void> didRenameFiles(RenameFilesParams params) async {
final requestData = params.toJson();

await _lspClientCommunication!.sendNotification(
'workspace/didRenameFiles',
requestData,
);
}
}

class LspJsonRpcClient {
Expand Down
2 changes: 2 additions & 0 deletions example/lib/lsp_exploration/lsp/messages/initialize.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,10 @@ class LspClientCapabilities {
'documentChanges': true,
'resourceOperations': ['create', 'rename', 'delete']
},

'fileOperations': {
'willRename': true,
'didRename': true,
}
},
};
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies:
analyzer: ^6.4.1
flutter:
sdk: flutter
path: ^1.9.0

dev_dependencies:
flutter_test:
Expand Down