laburnum 1.17.1

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0


// Large enough to contain any enumeration name defined in this crate
pub type PascalCaseBuf = [u8; 32];

pub const fn fmt_pascal_case_const(name: &str) -> (PascalCaseBuf, usize) {
  let mut buf = [0; 32];
  let mut buf_i = 0;
  let mut name_i = 0;
  let name = name.as_bytes();
  while name_i < name.len() {
    let first = name[name_i];
    name_i += 1;

    buf[buf_i] = first;
    buf_i += 1;

    while name_i < name.len() {
      let rest = name[name_i];
      name_i += 1;
      if rest == b'_' {
        break;
      }

      buf[buf_i] = rest.to_ascii_lowercase();
      buf_i += 1;
    }
  }
  (buf, buf_i)
}

pub fn fmt_pascal_case(
  f: &mut std::fmt::Formatter<'_>,
  name: &str,
) -> std::fmt::Result {
  for word in name.split('_') {
    let mut chars = word.chars();
    if let Some(first) = chars.next() {
      write!(f, "{first}")?;
    }
    for rest in chars {
      write!(f, "{}", rest.to_lowercase())?;
    }
  }
  Ok(())
}

// [Enumerations] section in LSP specification suggests to keep unknown values
// in enums and let them round trip.
//
// [Enumerations]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#enumerations
//
// ```
// struct SpecificCode(i32);
//
// lsp_enum! {
//   impl SpecificCode {
//     const FOO = 1;
//     const BAR = 2;
//   }
// }
// ```
macro_rules! lsp_enum {
    (
        impl $typ: ident {
            $(
                $(#[$attr:meta])*
                const $name:ident = $value:expr;
            )*
        }
    ) => {
        impl $typ {
            $(
                $(#[$attr])*
                pub const $name: $typ = $typ($value);
            )*
        }

        impl std::fmt::Debug for $typ {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                match *self {
                    $(
                        Self::$name => crate::protocol::macros::fmt_pascal_case(f, stringify!($name)),
                    )*
                    _ => write!(f, "{}({})", stringify!($typ), self.0),
                }
            }
        }

        impl std::fmt::Display for $typ {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                match *self {
                    $(
                        Self::$name => write!(f, "{}", $value),
                    )*
                    _ => write!(f, "{}", self.0),
                }
            }
        }

        impl std::convert::TryFrom<&str> for $typ {
            type Error = &'static str;
            fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
                match () {
                    $(
                        _ if {
                            const X: (crate::protocol::macros::PascalCaseBuf, usize) = crate::protocol::macros::fmt_pascal_case_const(stringify!($name));
                            let (buf, len) = X;
                            &buf[..len] == value.as_bytes()
                        } => Ok(Self::$name),
                    )*
                    _ => Err("unknown enum variant"),
                }
            }
        }

    }
}

pub(crate) use lsp_enum;

/// Macro to generate LSP request and notification routing match expressions.
///
/// This macro drastically reduces boilerplate for routing LSP requests and
/// notifications to their corresponding service trait methods. Instead of
/// writing ~7 lines of repetitive code per route, you can declare routes in a
/// simple, declarative style.
///
/// # Example
///
/// ```ignore
/// match_route! {
///   match method on self.server {
///     "textDocument/hover" => hover,
///     "textDocument/definition" => goto_definition,
///     @"textDocument/didOpen" => did_open,  // @ prefix for notifications
///   }
/// }
/// ```
///
/// This expands to the full routing implementation with proper error handling,
/// serialization/deserialization, and the default method_not_found handler.
///
/// Notifications (prefixed with @) don't send responses back to the client.
macro_rules! match_notification {
  (
    match $method:ident on $server_field:expr => {
      $(
        @ $notif_method:literal => $notif_rust_method:ident,
      )*
    }
  ) => {
    paste::paste! {
      match $method {
        $(
          | $notif_method => {
            let server = $server_field.clone();
            let handler: NotificationHandlerFn<P, T> =
              Box::new(move |_method, params, mut ctx, mut writer| {
                let server = server.clone();

                {
                  use opentelemetry::trace::FutureExt as _;

                  otel::span!(concat!("lsp.", $notif_method), in |cx| {
                    async move {
                      let params = match params {
                        | Some(p) => match serde_json::from_value(p) {
                          | Ok(params) => params,
                          | Err(_e) => {
                            return writer;
                          },
                        },
                        | None => {
                          return writer;
                        },
                      };

                      let mut ctx_ref = crate::database::PartitionWriteContextRef::new(&mut writer);
                      server.$notif_rust_method(params, &mut ctx, &mut ctx_ref).await;

                      writer
                    }
                    .with_context(cx)
                    .boxed()
                  })
                }
              });
            (handler, T::[<$notif_rust_method:snake:upper _LANE>])
          },
        )*
        | _ => {
          #[allow(unused)]
          let handler: NotificationHandlerFn<P, T> =
            Box::new(move |method, _, _, writer| {

              async move {
                writer
              }
              .boxed()
            });
          (handler, crate::scheduler::lanes::DEFAULT_LANE)
        },
      }
    }
  };
}

macro_rules! match_request {
  (
    match $method:ident on $server_field:expr => {
      $(
        $req_method:literal => $req_rust_method:ident,
      )*
    }
  ) => {
    paste::paste! {
      match $method {
        $(
          | $req_method => Self::route(
            $server_field.clone(),
            |server, params, mut ctx, mut writer| {
              use opentelemetry::trace::FutureExt as _;

              otel::span!(concat!("lsp.", $req_method), in |cx| {
                async move {
                  let mut ctx_ref = crate::database::PartitionWriteContextRef::new(&mut writer);
                  let result = server.$req_rust_method(params, &mut ctx, &mut ctx_ref).await;
                  (result, writer)
                }
                .with_context(cx)
              })
            },
            T::[<$req_rust_method:snake:upper _LANE>],
          ),
        )*
        | _ => {
          let handler: RequestHandlerFn<P, T> =
            Box::new(move |method, id, _, _, writer| {
              async move {
                let mut error = jsonrpc::Error::method_not_found();
                error.data = Some(LSPAny::from(method));
                let response = Response::from_error(id, error);
                (Some(response), writer)
              }
              .boxed()
            });
          (handler, crate::scheduler::lanes::DEFAULT_LANE)
        },
      }
    }
  };
}

pub(crate) use {
  match_notification,
  match_request,
};