just-lsp 0.4.2

A language server for just
use super::*;

#[derive(Debug)]
pub(crate) enum SettingKind {
  Array,
  Boolean(bool),
  String,
}

impl Display for SettingKind {
  fn fmt(&self, f: &mut Formatter) -> fmt::Result {
    match self {
      SettingKind::Array => write!(f, "array"),
      SettingKind::Boolean(_) => write!(f, "boolean"),
      SettingKind::String => write!(f, "string"),
    }
  }
}

impl PartialEq for SettingKind {
  fn eq(&self, other: &Self) -> bool {
    matches!(
      (self, other),
      (SettingKind::Array, SettingKind::Array)
        | (SettingKind::Boolean(_), SettingKind::Boolean(_))
        | (SettingKind::String, SettingKind::String)
    )
  }
}

#[derive(Debug, PartialEq)]
pub(crate) struct Setting {
  pub(crate) kind: SettingKind,
  pub(crate) name: String,
  pub(crate) range: lsp::Range,
}

impl Setting {
  #[must_use]
  pub(crate) fn from_node(node: &Node, document: &Document) -> Option<Self> {
    let range = node.get_range(document);

    let name = document.get_node_text(&node.child(1)?);

    let mut cursor = node.walk();

    let right_children = node
      .children_by_field_name("right", &mut cursor)
      .collect::<Vec<_>>();

    let has_bracket = right_children.iter().any(|child| child.kind() == "[");

    let boolean_child = right_children
      .iter()
      .find(|child| child.kind() == "boolean");

    let string_child =
      right_children.iter().find(|child| child.kind() == "string");

    let kind = if has_bracket {
      SettingKind::Array
    } else if let Some(boolean) = boolean_child {
      SettingKind::Boolean(document.get_node_text(boolean) == "true")
    } else if string_child.is_some() {
      SettingKind::String
    } else if right_children.is_empty() {
      SettingKind::Boolean(true)
    } else {
      return None;
    };

    Some(Setting { kind, name, range })
  }
}

#[cfg(test)]
mod tests {
  use {super::*, indoc::indoc, pretty_assertions::assert_eq};

  fn range(
    start_line: u32,
    start_character: u32,
    end_line: u32,
    end_character: u32,
  ) -> lsp::Range {
    lsp::Range {
      start: lsp::Position {
        line: start_line,
        character: start_character,
      },
      end: lsp::Position {
        line: end_line,
        character: end_character,
      },
    }
  }

  #[test]
  fn parse_boolean_with_value() {
    assert_eq!(
      Document::from("set foo := true\n").settings(),
      vec![Setting {
        name: "foo".to_string(),
        kind: SettingKind::Boolean(true),
        range: range(0, 0, 1, 0),
      }],
    );
  }

  #[test]
  fn parse_boolean_false() {
    assert_eq!(
      Document::from("set foo := false\n").settings(),
      vec![Setting {
        name: "foo".to_string(),
        kind: SettingKind::Boolean(false),
        range: range(0, 0, 1, 0),
      }],
    );
  }

  #[test]
  fn parse_boolean_without_value() {
    assert_eq!(
      Document::from("set export\n").settings(),
      vec![Setting {
        name: "export".to_string(),
        kind: SettingKind::Boolean(true),
        range: range(0, 0, 1, 0),
      }],
    );
  }

  #[test]
  fn parse_array() {
    assert_eq!(
      Document::from("set shell := [\"zsh\", \"-cu\"]\n").settings(),
      vec![Setting {
        name: "shell".to_string(),
        kind: SettingKind::Array,
        range: range(0, 0, 1, 0),
      }],
    );
  }

  #[test]
  fn parse_string() {
    assert_eq!(
      Document::from("set foo := \"bar\"\n").settings(),
      vec![Setting {
        name: "foo".to_string(),
        kind: SettingKind::String,
        range: range(0, 0, 1, 0),
      }],
    );
  }

  #[test]
  fn parse_string_containing_walrus() {
    assert_eq!(
      Document::from("set foo := \"bar := baz\"\n").settings(),
      vec![Setting {
        name: "foo".to_string(),
        kind: SettingKind::String,
        range: range(0, 0, 1, 0),
      }],
    );
  }

  #[test]
  fn parse_multiple_settings() {
    assert_eq!(
      Document::from(indoc! {"
        set foo := true
        set bar := \"baz\"
        set shell := [\"zsh\", \"-cu\"]
      "})
      .settings(),
      vec![
        Setting {
          name: "foo".to_string(),
          kind: SettingKind::Boolean(true),
          range: range(0, 0, 1, 0),
        },
        Setting {
          name: "bar".to_string(),
          kind: SettingKind::String,
          range: range(1, 0, 2, 0),
        },
        Setting {
          name: "shell".to_string(),
          kind: SettingKind::Array,
          range: range(2, 0, 3, 0),
        },
      ],
    );
  }
}