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
277 changes: 277 additions & 0 deletions packages/devtools_app/lib/src/service/editor/api_classes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:devtools_shared/devtools_shared.dart';

const editorServiceName = 'Editor';
const editorStreamName = 'Editor';
const lspServiceName = 'Lsp';

enum EditorMethod {
// Device.
Expand All @@ -20,6 +21,22 @@ enum EditorMethod {
openDevToolsPage,
}

/// Method names of LSP requests registered on the Analysis Server.
///
/// These should be kept in sync with the methods defined at
/// [pkg/analysis_server/lib/src/lsp/constants.dart][code link]
///
/// [code link]: https://github.com/dart-lang/sdk/blob/ebfcd436da65802a2b20d415afe600b51e432305/pkg/analysis_server/lib/src/lsp/constants.dart#L136
enum LspMethod {
editableArguments(
methodName: 'experimental/dart/textDocument/editableArguments',
);

const LspMethod({required this.methodName});

final String methodName;
}

/// Known kinds of events that may come from the editor.
///
/// This list is not guaranteed to match actual events from any given editor as
Expand Down Expand Up @@ -53,41 +70,64 @@ enum EditorEventKind {

/// The kind for a [ThemeChangedEvent].
themeChanged,

/// The kind for an [ActiveLocationChangedEvent] event.
activeLocationChanged,
}

/// Constants for all fields used in JSON maps to avoid literal strings that
/// may have typos sprinkled throughout the API classes.
abstract class Field {
static const active = 'active';
static const anchor = 'anchor';
static const arguments = 'arguments';
static const backgroundColor = 'backgroundColor';
static const category = 'category';
static const character = 'character';
static const debuggerType = 'debuggerType';
static const debugSession = 'debugSession';
static const debugSessionId = 'debugSessionId';
static const debugSessions = 'debugSessions';
static const device = 'device';
static const deviceId = 'deviceId';
static const devices = 'devices';
static const displayValue = 'displayValue';
static const emulator = 'emulator';
static const emulatorId = 'emulatorId';
static const ephemeral = 'ephemeral';
static const errorText = 'errorText';
static const flutterDeviceId = 'flutterDeviceId';
static const flutterMode = 'flutterMode';
static const fontSize = 'fontSize';
static const forceExternal = 'forceExternal';
static const foregroundColor = 'foregroundColor';
static const hasArgument = 'hasArgument';
static const id = 'id';
static const isDarkMode = 'isDarkMode';
static const isDefault = 'isDefault';
static const isEditable = 'isEditable';
static const isNullable = 'isNullable';
static const isRequired = 'isRequired';
static const line = 'line';
static const name = 'name';
static const options = 'options';
static const page = 'page';
static const platform = 'platform';
static const platformType = 'platformType';
static const prefersDebugSession = 'prefersDebugSession';
static const projectRootPath = 'projectRootPath';
static const requiresDebugSession = 'requiresDebugSession';
static const result = 'result';
static const selectedDeviceId = 'selectedDeviceId';
static const selections = 'selections';
static const supported = 'supported';
static const supportsForceExternal = 'supportsForceExternal';
static const textDocument = 'textDocument';
static const theme = 'theme';
static const type = 'type';
static const uri = 'uri';
static const value = 'value';
static const version = 'version';
static const vmServiceUri = 'vmServiceUri';
}

Expand Down Expand Up @@ -256,6 +296,243 @@ class ThemeChangedEvent extends EditorEvent {
Map<String, Object?> toJson() => {Field.theme: theme};
}

/// An event sent by an editor when the current cursor position/s change.
Copy link
Member

Choose a reason for hiding this comment

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

nit: "cursor position changes" ?

Copy link
Member Author

Choose a reason for hiding this comment

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

I was trying to specify that this could be either a single or multiple cursor positions.

class ActiveLocationChangedEvent extends EditorEvent {
ActiveLocationChangedEvent({
required this.selections,
required this.textDocument,
});

ActiveLocationChangedEvent.fromJson(Map<String, Object?> map)
: this(
textDocument: TextDocument.fromJson(
map[Field.textDocument] as Map<String, Object?>,
),
selections:
(map[Field.selections] as List<Object?>)
.cast<Map<String, Object?>>()
.map(EditorSelection.fromJson)
.toList(),
);

final List<EditorSelection> selections;
final TextDocument textDocument;

@override
EditorEventKind get kind => EditorEventKind.activeLocationChanged;

@override
Map<String, Object?> toJson() => {
Field.selections: selections,
Field.textDocument: textDocument,
};
}

/// A reference to a text document in the editor.
///
/// The [uriAsString] is a file URI to the text document.
///
/// The [version] is an integer corresponding to LSP's
/// [VersionedTextDocumentIdentifier](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#versionedTextDocumentIdentifier)
class TextDocument with Serializable {
TextDocument({required this.uriAsString, required this.version});

TextDocument.fromJson(Map<String, Object?> map)
: this(
uriAsString: map[Field.uri] as String,
version: map[Field.version] as int,
);

final String uriAsString;
final int version;

@override
Map<String, Object?> toJson() => {
Field.uri: uriAsString,
Field.version: version,
};

@override
bool operator ==(Object other) {
return other is TextDocument &&
other.uriAsString == uriAsString &&
other.version == version;
}

@override
int get hashCode => Object.hash(uriAsString, version);
}

/// The starting and ending cursor positions in the editor.
class EditorSelection with Serializable {
EditorSelection({required this.active, required this.anchor});

EditorSelection.fromJson(Map<String, Object?> map)
: this(
active: CursorPosition.fromJson(
map[Field.active] as Map<String, Object?>,
),
anchor: CursorPosition.fromJson(
map[Field.anchor] as Map<String, Object?>,
),
);

final CursorPosition active;
final CursorPosition anchor;

@override
Map<String, Object?> toJson() => {
Field.active: active.toJson(),
Field.anchor: anchor.toJson(),
};
}

/// Representation of a single cursor position in the editor.
///
/// The cursor position is after the given [character] of the [line].
class CursorPosition with Serializable {
CursorPosition({required this.character, required this.line});

CursorPosition.fromJson(Map<String, Object?> map)
: this(
character: map[Field.character] as int,
line: map[Field.line] as int,
);

/// The zero-based character number of this position.
final int character;

/// The zero-based line number of this position.
final int line;

@override
Map<String, Object?> toJson() => {
Field.character: character,
Field.line: line,
};

@override
bool operator ==(Object other) {
return other is CursorPosition &&
other.character == character &&
other.line == line;
}

@override
int get hashCode => Object.hash(character, line);
}

/// The result of an `editableArguments` request.
class EditableArgumentsResult with Serializable {
EditableArgumentsResult({required this.args});

EditableArgumentsResult.fromJson(Map<String, Object?> map)
: this(
args:
(map[Field.arguments] as List<Object?>? ?? <Object?>[])
.cast<Map<String, Object?>>()
.map(EditableArgument.fromJson)
.toList(),
);

final List<EditableArgument> args;

@override
Map<String, Object?> toJson() => {Field.arguments: args};
}

/// Information about a single editable argument of a widget.
class EditableArgument with Serializable {
EditableArgument({
required this.name,
required this.type,
required this.value,
required this.hasArgument,
required this.isDefault,
required this.isNullable,
required this.isRequired,
required this.isEditable,
this.options,
this.displayValue,
this.errorText,
});

EditableArgument.fromJson(Map<String, Object?> map)
: this(
name: map[Field.name] as String,
type: map[Field.type] as String,
value: map[Field.value],
hasArgument: (map[Field.hasArgument] as bool?) ?? false,
isDefault: (map[Field.isDefault] as bool?) ?? false,
isNullable: (map[Field.isNullable] as bool?) ?? false,
isRequired: (map[Field.isRequired] as bool?) ?? false,
isEditable: (map[Field.isEditable] as bool?) ?? true,
options: (map[Field.options] as List<Object?>?)?.cast<String>(),
displayValue: map[Field.displayValue] as String?,
errorText: map[Field.errorText] as String?,
);

/// The name of the corresponding parameter.
final String name;

/// The type of the corresponding parameter.
///
/// This is not necessarily the Dart type, it is from a defined set of values
/// that clients may understand how to edit.
final String type;

/// The current value for this argument.
final Object? value;

/// Whether an explicit argument exists for this parameter in the code.
final bool hasArgument;

/// Whether the value is the default for this parameter.
final bool isDefault;

/// Whether this argument can be `null`.
final bool isNullable;

/// Whether this argument is required.
final bool isRequired;

/// Whether this argument can be edited by the Analysis Server.
///
/// An argument might not be editable, e.g. if it is a positional parameter
/// where previous positional parameters have no argument.
final bool isEditable;

/// A list of values that could be provided for this argument.
///
/// This will only be included if the parameter's [type] is "enum".
final List<String>? options;

/// A string that can be displayed to indicate the value for this argument.
///
/// This is populated in cases where the source code is not literally the same
/// as the value field, for example an expression or named constant.
final String? displayValue;

final String? errorText;

String get valueDisplay => displayValue ?? value.toString();

@override
Map<String, Object?> toJson() => {
Field.name: name,
Field.type: type,
Field.value: value,
Field.hasArgument: hasArgument,
Field.isDefault: isDefault,
Field.isNullable: isNullable,
Field.isRequired: isRequired,
Field.isEditable: isEditable,
Field.options: options,
Field.displayValue: displayValue,
Field.errorText: errorText,
};
}

/// The result of a `GetDevices` request.
class GetDevicesResult with Serializable {
GetDevicesResult({required this.devices, required this.selectedDeviceId});
Expand Down
Loading
Loading