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
12 changes: 12 additions & 0 deletions generator-plugins/dotnet/custom/Direction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Enum)]
public class DirectionAttribute : Attribute
{
public DirectionAttribute(MessageDirection direction)
{
Direction = direction;
}

public MessageDirection Direction { get; }
}
9 changes: 0 additions & 9 deletions generator-plugins/dotnet/custom/ILSPResponse.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public interface ILSPMessage
public interface IMessage
{
string JsonRPC { get; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public interface ILSPNotification<TParams> : ILSPMessage
public interface INotification<TParams> : IMessage
{
string Method { get; }

Expand Down
15 changes: 15 additions & 0 deletions generator-plugins/dotnet/custom/IPartialResultParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

/// <summary>
/// Interface to describe parameters for requests that support streaming results.
///
/// See the <see href="https://microsoft.github.io/language-server-protocol/specifications/specification-current/#partialResultParams">Language Server Protocol specification</see> for additional information.
/// </summary>
/// <typeparam name="T">The type to be reported by <see cref="PartialResultToken"/>.</typeparam>
public interface IPartialResultParams
{
/// <summary>
/// An optional token that a server can use to report partial results (e.g. streaming) to the client.
/// </summary>
public ProgressToken? PartialResultToken { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public interface ILSPRequest<TParams> : ILSPMessage
public interface IRequest<TParams> : IMessage
{

OrType<int, string> Id { get; }
Expand Down
9 changes: 9 additions & 0 deletions generator-plugins/dotnet/custom/IResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public interface IResponse<TResponse> : IMessage
{

OrType<int, string> Id { get; }

TResponse Result { get; }

IResponseError? Error { get; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public interface ILSPResponseError
public interface IResponseError
{

int Code { get; }
Expand Down
8 changes: 8 additions & 0 deletions generator-plugins/dotnet/custom/MessageDirection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Runtime.Serialization;

public enum MessageDirection
{
[EnumMember(Value = "serverToClient")] ServerToClient,
[EnumMember(Value = "clientToServer")] ClientToServer,
[EnumMember(Value = "both")] Both,
}
55 changes: 52 additions & 3 deletions generator-plugins/dotnet/dotnet_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
get_special_case_property_name,
get_usings,
indent_lines,
lsp_method_to_name,
namespace_wrapper,
to_camel_case,
to_upper_camel_case,
Expand Down Expand Up @@ -182,14 +183,22 @@ def generate_property(
)
+ [
f'[DataMember(Name = "{prop_def.name}")]',
f"public {type_name}{optional} {name} {{ get; set; }}",
]
)

if prop_def.type.kind == "stringLiteral":
lines.append(
f'public {type_name}{optional} {name} {{ get; set; }} = "{prop_def.type.value}";'
)
else:
lines.append(f"public {type_name}{optional} {name} {{ get; set; }}")

usings.append("DataMember")
if converter:
usings.append("JsonConverter")
if optional and not special_optional:
usings.append("JsonProperty")

return lines, type_name


Expand Down Expand Up @@ -247,7 +256,6 @@ def generate_literal_type(
class_wrapper(literal, inner),
)
types.add_type_info(literal, literal.name, lines)
print(f"{name_context} => {literal.name}")
return literal.name


Expand Down Expand Up @@ -436,7 +444,6 @@ def generate_class_from_variant_literals(

lines = generate_code_for_variant_struct(struct, spec, types)
types.add_type_info(struct, struct.name, lines)
print(f"{name_context} => {struct.name}")
return struct.name


Expand Down Expand Up @@ -610,6 +617,46 @@ def get_all_properties(struct: model.Structure, spec) -> List[model.Structure]:
return properties


def generate_code_for_request(request: model.Request):
lines = get_doc(request.documentation) + generate_extras(request)
lines.append(
f'public static string {lsp_method_to_name(request.method)} {{ get; }} = "{request.method}";'
)
return lines


def generate_code_for_notification(notify: model.Notification):
lines = get_doc(notify.documentation) + generate_extras(notify)
lines.append(
f'public static string {lsp_method_to_name(notify.method)} {{ get; }} = "{notify.method}";'
)
return lines


def generate_request_notification_types(spec: model.LSPModel, types: TypeData):
inner_lines = []
for request in spec.requests:
inner_lines += generate_code_for_request(request)

for notification in spec.notifications:
inner_lines += generate_code_for_notification(notification)

lines = namespace_wrapper(
NAMESPACE,
get_usings(["System"]),
["public static class LSPMethods", "{", *indent_lines(inner_lines), "}"],
)
enum_type = model.Enum(
**{
"name": "LSPMethods",
"type": {"kind": "base", "name": "string"},
"values": [],
"documentation": "LSP methods as defined in the LSP spec",
}
)
types.add_type_info(enum_type, "LSPMethods", lines)


def generate_all_classes(spec: model.LSPModel, types: TypeData):
for struct in spec.structures:
generate_class_from_struct(struct, spec, types)
Expand All @@ -619,3 +666,5 @@ def generate_all_classes(spec: model.LSPModel, types: TypeData):
generate_class_from_variant_type_alias(type_alias, spec, types)
else:
generate_class_from_type_alias(type_alias, spec, types)

generate_request_notification_types(spec, types)
2 changes: 1 addition & 1 deletion generator-plugins/dotnet/dotnet_constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

NAMESPACE = "LSProtocol"
NAMESPACE = "Microsoft.LanguageServer.Protocol"
PACKAGE_DIR_NAME = "lsprotocol"
27 changes: 25 additions & 2 deletions generator-plugins/dotnet/dotnet_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ def to_upper_camel_case(name: str) -> str:
return "".join([c.capitalize() for c in get_parts(name)])


def lsp_method_to_name(method: str) -> str:
if method.startswith("$"):
method = method[1:]
method = method.replace("/", "_")
return to_upper_camel_case(method)


def file_header() -> List[str]:
return [
"// Copyright (c) Microsoft Corporation. All rights reserved.",
Expand Down Expand Up @@ -134,7 +141,13 @@ def get_deprecated(text: Optional[str]) -> Optional[str]:

def generate_extras(
type_def: Union[
model.Enum, model.EnumItem, model.Property, model.TypeAlias, model.Structure
model.Enum,
model.EnumItem,
model.Property,
model.TypeAlias,
model.Structure,
model.Request,
model.Notification,
]
) -> List[str]:
deprecated = get_deprecated(type_def.documentation)
Expand All @@ -148,6 +161,12 @@ def generate_extras(
if type_def.since:
extras += [f'[Since("{cleanup_str(type_def.since)}")]']

if hasattr(type_def, "messageDirection"):
if type_def.since:
extras += [
f"[Direction(MessageDirection.{to_upper_camel_case(type_def.messageDirection)})]"
]

return extras


Expand All @@ -166,4 +185,8 @@ def get_usings(types: List[str]) -> List[str]:
if t in types:
usings.append("using Newtonsoft.Json.Linq;")

return list(set(usings))
for t in ["List", "Dictionary"]:
if t in types:
usings.append("using System.Collections.Generic;")

return sorted(list(set(usings)))
8 changes: 4 additions & 4 deletions generator-plugins/dotnet/dotnet_special_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ def generate_special_class(
if type_def.name == "LSPObject":
lines = namespace_wrapper(
NAMESPACE,
get_usings(["JObject", "DataContract"]),
class_wrapper(type_def, [], "JObject"),
get_usings(["Dictionary", "DataContract"]),
class_wrapper(type_def, [], "Dictionary<string, object?>"),
)
if type_def.name == "LSPAny":
lines = namespace_wrapper(
Expand All @@ -50,8 +50,8 @@ def generate_special_class(
if type_def.name == "LSPArray":
lines = namespace_wrapper(
NAMESPACE,
get_usings(["JArray", "DataContract"]),
class_wrapper(type_def, [], "JArray"),
get_usings(["DataContract", "List"]),
class_wrapper(type_def, [], "List<object>"),
)

if type_def.name == "Pattern":
Expand Down
5 changes: 5 additions & 0 deletions generator-plugins/dotnet/dotnet_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import logging
import os
import pathlib
import subprocess
Expand All @@ -15,12 +16,15 @@
from .dotnet_helpers import namespace_wrapper
from .dotnet_special_classes import generate_special_classes

LOGGER = logging.getLogger("dotnet")


def generate_from_spec(spec: model.LSPModel, output_dir: str) -> None:
"""Generate the code for the given spec."""
cleanup(output_dir)
copy_custom_classes(output_dir)

LOGGER.info("Generating code in C#")
types = TypeData()
generate_package_code(spec, types)
for name, lines in types.get_all():
Expand All @@ -29,6 +33,7 @@ def generate_from_spec(spec: model.LSPModel, output_dir: str) -> None:
"\n".join(lines), encoding="utf-8"
)

LOGGER.info("Running dotnet format")
subprocess.run(
["dotnet", "format"], cwd=os.fspath(pathlib.Path(output_dir, PACKAGE_DIR_NAME))
)
Expand Down
18 changes: 10 additions & 8 deletions generator/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def get_parser() -> argparse.ArgumentParser:
"-m",
help="Path to a model JSON file. By default uses packaged model file.",
type=str,
nargs="*",
)
parser.add_argument(
"--plugin",
Expand All @@ -68,15 +69,16 @@ def main(argv: Sequence[str]) -> None:
schema = json.load(schema_file.open("rb"))

if args.model:
model_file = pathlib.Path(args.model)
model_files = [pathlib.Path(m) for m in args.model]
else:
model_file = ir.files("generator") / "lsp.json"
model_files = [ir.files("generator") / "lsp.json"]

LOGGER.info("Using model file %s", os.fspath(model_file))
json_model = json.load(model_file.open("rb"))

LOGGER.info("Validating model.")
jsonschema.validate(json_model, schema)
json_models = []
for model_file in model_files:
LOGGER.info("Validating model file %s", os.fspath(model_file))
json_model = json.load(model_file.open("rb"))
jsonschema.validate(json_model, schema)
json_models.append(json_model)

plugins = args.plugin or []

Expand All @@ -99,7 +101,7 @@ def main(argv: Sequence[str]) -> None:

# load model and generate types for each plugin to avoid
# any conflicts between plugins.
spec: model.LSPModel = model.create_lsp_model(json_model)
spec: model.LSPModel = model.create_lsp_model(json_models)

try:
plugin_module = importlib.import_module(f"generator-plugins.{plugin}")
Expand Down
16 changes: 14 additions & 2 deletions generator/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -881,5 +881,17 @@ def get_inner_types(self) -> List[Type]:
return types


def create_lsp_model(model: Dict[str, Any]) -> LSPModel:
return LSPModel(**model)
def create_lsp_model(models: List[Dict[str, Any]]) -> LSPModel:
if len(models) >= 1:
spec = LSPModel(**models[0])

if len(models) >= 2:
for model in models[1:]:
addition = LSPModel(**model)
spec.requests.extend(addition.requests)
spec.notifications.extend(addition.notifications)
spec.structures.extend(addition.structures)
spec.enumerations.extend(addition.enumerations)
spec.typeAliases.extend(addition.typeAliases)

return spec
Loading