2019-07-29 23:02:49 +00:00
|
|
|
import 'dart:async';
|
2021-02-16 02:10:09 +00:00
|
|
|
//import 'package:dart_language_server/src/protocol/language_server/interface.dart';
|
|
|
|
//import 'package:dart_language_server/src/protocol/language_server/messages.dart';
|
2019-07-29 23:02:49 +00:00
|
|
|
import 'package:file/file.dart';
|
|
|
|
import 'package:file/local.dart';
|
|
|
|
import 'package:file/memory.dart';
|
|
|
|
import 'package:jael/jael.dart';
|
|
|
|
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc_2;
|
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:path/path.dart' as p;
|
|
|
|
import 'package:source_span/source_span.dart';
|
|
|
|
import 'package:string_scanner/string_scanner.dart';
|
|
|
|
import 'package:symbol_table/symbol_table.dart';
|
|
|
|
import 'analyzer.dart';
|
|
|
|
import 'object.dart';
|
2021-02-16 02:10:09 +00:00
|
|
|
import 'protocol/language_server/interface.dart';
|
|
|
|
import 'protocol/language_server/messages.dart';
|
2019-07-29 23:02:49 +00:00
|
|
|
|
|
|
|
class JaelLanguageServer extends LanguageServer {
|
2021-06-20 12:37:20 +00:00
|
|
|
final _diagnostics = StreamController<Diagnostics>();
|
|
|
|
final _done = Completer();
|
|
|
|
final _memFs = MemoryFileSystem();
|
|
|
|
final _localFs = const LocalFileSystem();
|
|
|
|
Directory? _localRootDir, _memRootDir;
|
|
|
|
var logger = Logger('jael');
|
|
|
|
late Uri _rootUri;
|
|
|
|
final _workspaceEdits = StreamController<ApplyWorkspaceEditParams>();
|
2019-07-29 23:02:49 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
Stream<Diagnostics> get diagnostics => _diagnostics.stream;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> get onDone => _done.future;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Stream<ApplyWorkspaceEditParams> get workspaceEdits => _workspaceEdits.stream;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> shutdown() {
|
|
|
|
if (!_done.isCompleted) _done.complete();
|
|
|
|
_diagnostics.close();
|
|
|
|
_workspaceEdits.close();
|
|
|
|
return super.shutdown();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void setupExtraMethods(json_rpc_2.Peer peer) {
|
|
|
|
peer.registerMethod('textDocument/formatting',
|
|
|
|
(json_rpc_2.Parameters params) async {
|
|
|
|
var documentId =
|
2021-06-20 12:37:20 +00:00
|
|
|
TextDocumentIdentifier.fromJson(params['textDocument'].asMap);
|
2019-07-29 23:02:49 +00:00
|
|
|
var formattingOptions =
|
2021-06-20 12:37:20 +00:00
|
|
|
FormattingOptions.fromJson(params['options'].asMap);
|
2019-07-29 23:02:49 +00:00
|
|
|
return await textDocumentFormatting(documentId, formattingOptions);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
Future<ServerCapabilities> initialize(int? clientPid, String? rootUri,
|
|
|
|
ClientCapabilities clientCapabilities, String? trace) async {
|
2019-07-29 23:02:49 +00:00
|
|
|
// Find our real root dir.
|
2021-06-20 12:37:20 +00:00
|
|
|
_localRootDir = _localFs.directory(_rootUri = Uri.parse(rootUri!));
|
2019-07-29 23:02:49 +00:00
|
|
|
_memRootDir = _memFs.directory('/');
|
2021-06-20 12:37:20 +00:00
|
|
|
await _memRootDir!.create(recursive: true);
|
2019-07-29 23:02:49 +00:00
|
|
|
_memFs.currentDirectory = _memRootDir;
|
|
|
|
|
|
|
|
// Copy all real files that end in *.jael (and *.jl for legacy) into the in-memory filesystem.
|
2021-06-20 12:37:20 +00:00
|
|
|
await for (var entity in _localRootDir!.list(recursive: true)) {
|
2019-07-29 23:02:49 +00:00
|
|
|
if (entity is File && p.extension(entity.path) == '.jael') {
|
|
|
|
logger.info('HEY ${entity.path}');
|
|
|
|
var file = _memFs.file(entity.absolute.path);
|
|
|
|
await file.create(recursive: true);
|
|
|
|
await entity
|
|
|
|
.openRead()
|
|
|
|
.cast<List<int>>()
|
|
|
|
.pipe(file.openWrite(mode: FileMode.write));
|
|
|
|
logger.info(
|
|
|
|
'Found Jael file ${file.path}; copied to ${file.absolute.path}');
|
|
|
|
|
|
|
|
// Analyze it
|
2021-06-20 12:37:20 +00:00
|
|
|
var documentId = TextDocumentIdentifier((b) {
|
|
|
|
b.uri = _rootUri.replace(path: file.path).toString();
|
2019-07-29 23:02:49 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
await analyzerForId(documentId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
return ServerCapabilities((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..codeActionProvider = false
|
2021-06-20 12:37:20 +00:00
|
|
|
..completionProvider = CompletionOptions((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..resolveProvider = true
|
|
|
|
..triggerCharacters =
|
|
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdeghijklmnopqrstuvxwyz'
|
|
|
|
.codeUnits
|
2021-06-20 12:37:20 +00:00
|
|
|
.map((c) => String.fromCharCode(c))
|
2019-07-29 23:02:49 +00:00
|
|
|
.toList();
|
|
|
|
})
|
|
|
|
..definitionProvider = true
|
|
|
|
..documentHighlightProvider = true
|
|
|
|
..documentRangeFormattingProvider = false
|
|
|
|
..documentOnTypeFormattingProvider = null
|
|
|
|
..documentSymbolProvider = true
|
|
|
|
..documentFormattingProvider = true
|
|
|
|
..hoverProvider = true
|
|
|
|
..implementationProvider = true
|
|
|
|
..referencesProvider = true
|
|
|
|
..renameProvider = true
|
2021-06-20 12:37:20 +00:00
|
|
|
..signatureHelpProvider = SignatureHelpOptions((b) {})
|
|
|
|
..textDocumentSync = TextDocumentSyncOptions((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..openClose = true
|
|
|
|
..change = TextDocumentSyncKind.full
|
2021-06-20 12:37:20 +00:00
|
|
|
..save = SaveOptions((b) {
|
|
|
|
b.includeText = false;
|
2019-07-29 23:02:49 +00:00
|
|
|
})
|
|
|
|
..willSave = false
|
|
|
|
..willSaveWaitUntil = false;
|
|
|
|
})
|
|
|
|
..workspaceSymbolProvider = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<File> fileForId(TextDocumentIdentifier documentId) async {
|
2021-06-20 12:37:20 +00:00
|
|
|
var uri = Uri.parse(documentId.uri!);
|
2019-07-29 23:02:49 +00:00
|
|
|
var relativePath = uri.path;
|
|
|
|
var file = _memFs.directory('/').childFile(relativePath);
|
|
|
|
|
|
|
|
/*
|
|
|
|
logger.info('Searching for $relativePath. All:\n');
|
|
|
|
|
|
|
|
await for (var entity in _memFs.directory('/').list(recursive: true)) {
|
|
|
|
if (entity is File) print(' * ${entity.absolute.path}');
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!await file.exists()) {
|
|
|
|
await file.create(recursive: true);
|
|
|
|
await _localFs
|
|
|
|
.file(uri)
|
|
|
|
.openRead()
|
|
|
|
.cast<List<int>>()
|
|
|
|
.pipe(file.openWrite());
|
|
|
|
logger.info('Opened Jael file ${file.path}');
|
|
|
|
}
|
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Scanner> scannerForId(TextDocumentIdentifier documentId) async {
|
|
|
|
var file = await fileForId(documentId);
|
|
|
|
return scan(await file.readAsString(), sourceUrl: file.uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Analyzer> analyzerForId(TextDocumentIdentifier documentId) async {
|
|
|
|
var scanner = await scannerForId(documentId);
|
2021-06-20 12:37:20 +00:00
|
|
|
var analyzer = Analyzer(scanner, logger)..errors.addAll(scanner.errors);
|
2019-07-29 23:02:49 +00:00
|
|
|
analyzer.parseDocument();
|
|
|
|
emitDiagnostics(documentId.uri, analyzer.errors.map(toDiagnostic).toList());
|
|
|
|
return analyzer;
|
|
|
|
}
|
|
|
|
|
|
|
|
Diagnostic toDiagnostic(JaelError e) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return Diagnostic((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..message = e.message
|
|
|
|
..range = toRange(e.span)
|
|
|
|
..severity = toSeverity(e.severity)
|
|
|
|
..source = e.span.start.sourceUrl.toString();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
int toSeverity(JaelErrorSeverity s) {
|
|
|
|
switch (s) {
|
|
|
|
case JaelErrorSeverity.warning:
|
|
|
|
return DiagnosticSeverity.warning;
|
|
|
|
default:
|
|
|
|
return DiagnosticSeverity.error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Range toRange(FileSpan span) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return Range((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..start = toPosition(span.start)
|
|
|
|
..end = toPosition(span.end);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Range emptyRange() {
|
2021-06-20 12:37:20 +00:00
|
|
|
return Range((b) => b
|
|
|
|
..start = b.end = Position((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..character = 1
|
|
|
|
..line = 0;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
Position toPosition(SourceLocation location) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return Position((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..line = location.line
|
|
|
|
..character = location.column;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
Location toLocation(String? uri, FileSpan span) {
|
|
|
|
return Location((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..range = toRange(span)
|
|
|
|
..uri = uri;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isReachable(JaelObject obj, Position position) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return obj.span.start.line <= position.line! &&
|
|
|
|
obj.span.start.column <= position.character!;
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
CompletionItem? toCompletion(Variable<JaelObject> symbol) {
|
2019-07-29 23:02:49 +00:00
|
|
|
var value = symbol.value;
|
|
|
|
|
|
|
|
if (value is JaelCustomElement) {
|
|
|
|
var name = value.name;
|
2021-06-20 12:37:20 +00:00
|
|
|
return CompletionItem((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..kind = CompletionItemKind.classKind
|
|
|
|
..label = symbol.name
|
2021-06-20 12:37:20 +00:00
|
|
|
..textEdit = TextEdit((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..range = emptyRange()
|
|
|
|
..newText = '<$name\$1>\n \$2\n</name>';
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else if (value is JaelVariable) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return CompletionItem((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..kind = CompletionItemKind.variable
|
|
|
|
..label = symbol.name;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
void emitDiagnostics(String? uri, Iterable<Diagnostic> diagnostics) {
|
|
|
|
_diagnostics.add(Diagnostics((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
logger.info('$uri => ${diagnostics.map((d) => d.message).toList()}');
|
|
|
|
b
|
|
|
|
..diagnostics = diagnostics.toList()
|
|
|
|
..uri = uri.toString();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future textDocumentDidOpen(TextDocumentItem document) async {
|
2021-06-20 12:37:20 +00:00
|
|
|
await analyzerForId(TextDocumentIdentifier((b) => b..uri = document.uri));
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future textDocumentDidChange(VersionedTextDocumentIdentifier documentId,
|
|
|
|
List<TextDocumentContentChangeEvent> changes) async {
|
2021-06-20 12:37:20 +00:00
|
|
|
var id = TextDocumentIdentifier((b) => b..uri = documentId.uri);
|
2019-07-29 23:02:49 +00:00
|
|
|
var file = await fileForId(id);
|
|
|
|
|
|
|
|
for (var change in changes) {
|
|
|
|
if (change.text != null) {
|
2021-06-20 12:37:20 +00:00
|
|
|
await file.writeAsString(change.text!);
|
2019-07-29 23:02:49 +00:00
|
|
|
} else if (change.range != null) {
|
2021-06-20 12:37:20 +00:00
|
|
|
String? contents = await file.readAsString();
|
2019-07-29 23:02:49 +00:00
|
|
|
|
|
|
|
int findIndex(Position position) {
|
2021-06-20 12:37:20 +00:00
|
|
|
var lines = contents!.split('\n');
|
2019-07-29 23:02:49 +00:00
|
|
|
|
|
|
|
// Sum the length of the previous lines.
|
2021-06-20 12:37:20 +00:00
|
|
|
var lineLength = lines
|
|
|
|
.take(position.line! - 1)
|
2019-07-29 23:02:49 +00:00
|
|
|
.map((s) => s.length)
|
|
|
|
.reduce((a, b) => a + b);
|
2021-06-20 12:37:20 +00:00
|
|
|
return lineLength + position.character! - 1;
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (change.range == null) {
|
|
|
|
contents = change.text;
|
|
|
|
} else {
|
2021-06-20 12:37:20 +00:00
|
|
|
var start = findIndex(change.range!.start!),
|
|
|
|
end = findIndex(change.range!.end!);
|
|
|
|
contents = contents.replaceRange(start, end, change.text!);
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
logger.info('${file.path} => $contents');
|
2021-06-20 12:37:20 +00:00
|
|
|
await file.writeAsString(contents!);
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await analyzerForId(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<List> textDocumentCodeAction(TextDocumentIdentifier documentId,
|
|
|
|
Range range, CodeActionContext context) async {
|
|
|
|
// TODO: implement textDocumentCodeAction
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<CompletionList> textDocumentCompletion(
|
|
|
|
TextDocumentIdentifier documentId, Position position) async {
|
|
|
|
var analyzer = await analyzerForId(documentId);
|
2021-06-20 12:37:20 +00:00
|
|
|
var symbols = analyzer.scope!.allVariables;
|
|
|
|
var reachable = symbols.where((s) => isReachable(s.value!, position));
|
|
|
|
return CompletionList((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..isIncomplete = false
|
|
|
|
..items = reachable.map(toCompletion).toList();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
final RegExp _id =
|
2021-06-20 12:37:20 +00:00
|
|
|
RegExp(r'(([A-Za-z][A-Za-z0-9_]*-)*([A-Za-z][A-Za-z0-9_]*))');
|
2019-07-29 23:02:49 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
Future<String?> currentName(
|
2019-07-29 23:02:49 +00:00
|
|
|
TextDocumentIdentifier documentId, Position position) async {
|
|
|
|
// First, read the file.
|
|
|
|
var file = await fileForId(documentId);
|
|
|
|
var contents = await file.readAsString();
|
|
|
|
|
|
|
|
// Next, find the current index.
|
2021-06-20 12:37:20 +00:00
|
|
|
var scanner = SpanScanner(contents);
|
2019-07-29 23:02:49 +00:00
|
|
|
|
|
|
|
while (!scanner.isDone &&
|
|
|
|
(scanner.state.line != position.line ||
|
|
|
|
scanner.state.column != position.character)) {
|
|
|
|
scanner.readChar();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, just read the name.
|
|
|
|
if (scanner.matches(_id)) {
|
2021-06-20 12:37:20 +00:00
|
|
|
var longest = scanner.lastSpan!.text;
|
2019-07-29 23:02:49 +00:00
|
|
|
|
|
|
|
while (scanner.matches(_id) && scanner.position > 0 && !scanner.isDone) {
|
2021-06-20 12:37:20 +00:00
|
|
|
longest = scanner.lastSpan!.text;
|
2019-07-29 23:02:49 +00:00
|
|
|
scanner.position--;
|
|
|
|
}
|
|
|
|
|
|
|
|
return longest;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
Future<JaelObject?> currentSymbol(
|
2019-07-29 23:02:49 +00:00
|
|
|
TextDocumentIdentifier documentId, Position position) async {
|
|
|
|
var name = await currentName(documentId, position);
|
|
|
|
if (name == null) return null;
|
|
|
|
var analyzer = await analyzerForId(documentId);
|
2021-06-20 12:37:20 +00:00
|
|
|
var symbols = analyzer.allDefinitions; // ?? analyzer.scope!.allVariables;
|
2019-07-29 23:02:49 +00:00
|
|
|
logger
|
|
|
|
.info('Current symbols, seeking $name: ${symbols.map((v) => v.name)}');
|
2021-06-20 12:37:20 +00:00
|
|
|
return analyzer.scope!.resolve(name)?.value;
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
Future<Location?> textDocumentDefinition(
|
2019-07-29 23:02:49 +00:00
|
|
|
TextDocumentIdentifier documentId, Position position) async {
|
|
|
|
var symbol = await currentSymbol(documentId, position);
|
|
|
|
if (symbol != null) {
|
|
|
|
return toLocation(documentId.uri, symbol.span);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<List<DocumentHighlight>> textDocumentHighlight(
|
|
|
|
TextDocumentIdentifier documentId, Position position) async {
|
|
|
|
var symbol = await currentSymbol(documentId, position);
|
|
|
|
if (symbol != null) {
|
|
|
|
return symbol.usages.map((u) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return DocumentHighlight((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..range = toRange(u.span)
|
|
|
|
..kind = u.type == SymbolUsageType.definition
|
|
|
|
? DocumentHighlightKind.write
|
|
|
|
: DocumentHighlightKind.read;
|
|
|
|
});
|
|
|
|
}).toList();
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
Future<Hover?> textDocumentHover(
|
2019-07-29 23:02:49 +00:00
|
|
|
TextDocumentIdentifier documentId, Position position) async {
|
|
|
|
var symbol = await currentSymbol(documentId, position);
|
|
|
|
if (symbol != null) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return Hover((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..contents = symbol.span.text
|
|
|
|
..range = toRange(symbol.span);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<List<Location>> textDocumentImplementation(
|
|
|
|
TextDocumentIdentifier documentId, Position position) async {
|
|
|
|
var defn = await textDocumentDefinition(documentId, position);
|
|
|
|
return defn == null ? [] : [defn];
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<List<Location>> textDocumentReferences(
|
|
|
|
TextDocumentIdentifier documentId,
|
|
|
|
Position position,
|
|
|
|
ReferenceContext context) async {
|
|
|
|
var symbol = await currentSymbol(documentId, position);
|
|
|
|
if (symbol != null) {
|
|
|
|
return symbol.usages.map((u) {
|
|
|
|
return toLocation(documentId.uri, u.span);
|
|
|
|
}).toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<WorkspaceEdit> textDocumentRename(TextDocumentIdentifier documentId,
|
2021-06-20 12:37:20 +00:00
|
|
|
Position position, String? newName) async {
|
2019-07-29 23:02:49 +00:00
|
|
|
var symbol = await currentSymbol(documentId, position);
|
|
|
|
if (symbol != null) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return WorkspaceEdit((b) {
|
|
|
|
b.changes = {
|
|
|
|
symbol.name: symbol.usages.map((u) {
|
|
|
|
return TextEdit((b) {
|
|
|
|
b
|
|
|
|
..range = toRange(u.span)
|
|
|
|
..newText = (symbol is JaelCustomElement &&
|
|
|
|
u.type == SymbolUsageType.definition)
|
|
|
|
? '"$newName"'
|
|
|
|
: newName;
|
|
|
|
});
|
|
|
|
}).toList()
|
|
|
|
};
|
2019-07-29 23:02:49 +00:00
|
|
|
});
|
|
|
|
}
|
2021-06-20 12:37:20 +00:00
|
|
|
return WorkspaceEdit((b) {
|
|
|
|
b.changes = {};
|
2019-07-29 23:02:49 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<List<SymbolInformation>> textDocumentSymbols(
|
|
|
|
TextDocumentIdentifier documentId) async {
|
|
|
|
var analyzer = await analyzerForId(documentId);
|
|
|
|
return analyzer.allDefinitions.map((symbol) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return SymbolInformation((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..kind = SymbolKind.classSymbol
|
|
|
|
..name = symbol.name
|
2021-06-20 12:37:20 +00:00
|
|
|
..location = toLocation(documentId.uri, symbol.value!.span);
|
2019-07-29 23:02:49 +00:00
|
|
|
});
|
|
|
|
}).toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
Future<void> workspaceExecuteCommand(String? command, List? arguments) async {
|
2019-07-29 23:02:49 +00:00
|
|
|
// TODO: implement workspaceExecuteCommand
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
Future<List<SymbolInformation>> workspaceSymbol(String? query) async {
|
|
|
|
var values = <JaelObject?>[];
|
2019-07-29 23:02:49 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
await for (var file in _memRootDir!.list(recursive: true)) {
|
2019-07-29 23:02:49 +00:00
|
|
|
if (file is File) {
|
2021-06-20 12:37:20 +00:00
|
|
|
var id = TextDocumentIdentifier((b) {
|
|
|
|
b.uri = file.uri.toString();
|
2019-07-29 23:02:49 +00:00
|
|
|
});
|
|
|
|
var analyzer = await analyzerForId(id);
|
|
|
|
values.addAll(analyzer.allDefinitions.map((v) => v.value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return values.map((o) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return SymbolInformation((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
2021-06-20 12:37:20 +00:00
|
|
|
..name = o!.name
|
2019-07-29 23:02:49 +00:00
|
|
|
..location = toLocation(o.span.sourceUrl.toString(), o.span)
|
2021-06-20 12:37:20 +00:00
|
|
|
..containerName = p.basename(o.span.sourceUrl!.path)
|
2019-07-29 23:02:49 +00:00
|
|
|
..kind = o is JaelCustomElement
|
|
|
|
? SymbolKind.classSymbol
|
|
|
|
: SymbolKind.variable;
|
|
|
|
});
|
|
|
|
}).toList();
|
|
|
|
}
|
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
Future<List<TextEdit>?> textDocumentFormatting(
|
2019-07-29 23:02:49 +00:00
|
|
|
TextDocumentIdentifier documentId,
|
|
|
|
FormattingOptions formattingOptions) async {
|
|
|
|
try {
|
|
|
|
var errors = <JaelError>[];
|
|
|
|
var file = await fileForId(documentId);
|
|
|
|
var contents = await file.readAsString();
|
|
|
|
var document =
|
|
|
|
parseDocument(contents, sourceUrl: file.uri, onError: errors.add);
|
|
|
|
if (errors.isNotEmpty) return null;
|
2021-06-20 12:37:20 +00:00
|
|
|
var formatter = JaelFormatter(
|
|
|
|
formattingOptions.tabSize!, formattingOptions.insertSpaces, 80);
|
|
|
|
var formatted = formatter.apply(document!);
|
|
|
|
logger.info('Original:$contents\nFormatted:\n$formatted');
|
2019-07-29 23:02:49 +00:00
|
|
|
if (formatted.isNotEmpty) await file.writeAsString(formatted);
|
|
|
|
return [
|
2021-06-20 12:37:20 +00:00
|
|
|
TextEdit((b) {
|
2019-07-29 23:02:49 +00:00
|
|
|
b
|
|
|
|
..newText = formatted
|
2021-06-20 12:37:20 +00:00
|
|
|
..range = toRange(document
|
|
|
|
.span); //document == null ? emptyRange() : toRange(document.span);
|
2019-07-29 23:02:49 +00:00
|
|
|
})
|
|
|
|
];
|
|
|
|
} catch (e, st) {
|
|
|
|
logger.severe('Formatter error', e, st);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initialized() {
|
|
|
|
// TODO: implement initialized
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
// TODO: implement showMessages
|
2021-06-20 12:37:20 +00:00
|
|
|
Stream<ShowMessageParams>? get showMessages => null;
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
abstract class DiagnosticSeverity {
|
|
|
|
static const int error = 0, warning = 1, information = 2, hint = 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
class FormattingOptions {
|
2021-06-20 12:37:20 +00:00
|
|
|
final num? tabSize;
|
2019-07-29 23:02:49 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
final bool? insertSpaces;
|
2019-07-29 23:02:49 +00:00
|
|
|
|
|
|
|
FormattingOptions(this.tabSize, this.insertSpaces);
|
|
|
|
|
|
|
|
factory FormattingOptions.fromJson(Map json) {
|
2021-06-20 12:37:20 +00:00
|
|
|
return FormattingOptions(
|
|
|
|
json['tabSize'] as num?, json['insertSpaces'] as bool?);
|
2019-07-29 23:02:49 +00:00
|
|
|
}
|
|
|
|
}
|