use std::cmp::Ordering;
use std::fmt;
use cairo_lang_filesystem::span::TextWidth;
use cairo_lang_syntax as syntax;
use cairo_lang_syntax::attribute::consts::FMT_SKIP_ATTR;
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::{ast, SyntaxNode, Terminal, TypedSyntaxNode};
use itertools::Itertools;
use smol_str::SmolStr;
use syntax::node::ast::MaybeModuleBody;
use syntax::node::helpers::QueryAttrs;
use syntax::node::kind::SyntaxKind;
use crate::FormatterConfig;
#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum BreakLinePointIndentation {
Indented,
IndentedWithTail,
NotIndented,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BreakLinePointProperties {
pub is_empty_line_breakpoint: bool,
pub precedence: usize,
pub break_indentation: BreakLinePointIndentation,
pub is_optional: bool,
pub space_if_not_broken: bool,
pub is_single_breakpoint: bool,
}
impl Ord for BreakLinePointProperties {
fn cmp(&self, other: &Self) -> Ordering {
match (self.is_empty_line_breakpoint, other.is_empty_line_breakpoint) {
(true, true) | (false, false) => self.precedence.cmp(&other.precedence),
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
}
}
}
impl PartialOrd for BreakLinePointProperties {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl BreakLinePointProperties {
pub fn new(
precedence: usize,
break_indentation: BreakLinePointIndentation,
is_optional: bool,
space_if_not_broken: bool,
) -> Self {
Self {
precedence,
break_indentation,
is_optional,
space_if_not_broken,
is_empty_line_breakpoint: false,
is_single_breakpoint: false,
}
}
pub fn new_empty_line() -> Self {
Self {
precedence: 0,
break_indentation: BreakLinePointIndentation::NotIndented,
is_optional: false,
space_if_not_broken: false,
is_empty_line_breakpoint: true,
is_single_breakpoint: false,
}
}
pub fn set_single_breakpoint(&mut self) {
self.is_single_breakpoint = true;
}
}
#[derive(Clone, Debug)]
enum LineComponent {
Token(String),
ProtectedZone { builder: LineBuilder, precedence: usize },
Space,
Indent(usize),
BreakLinePoint(BreakLinePointProperties),
Comment { content: String, is_trailing: bool },
}
impl LineComponent {
pub fn width(&self) -> usize {
match self {
Self::Token(s) => s.len(),
Self::ProtectedZone { builder, .. } => builder.width(),
Self::Space => 1,
Self::Indent(n) => *n,
Self::BreakLinePoint(properties) => usize::from(properties.space_if_not_broken),
Self::Comment { content, is_trailing } => {
if *is_trailing {
content.len()
} else {
0
}
}
}
}
fn is_trivia(&self) -> bool {
matches!(
self,
Self::Comment { .. } | Self::Space | Self::Indent(_) | Self::BreakLinePoint(_)
)
}
}
impl fmt::Display for LineComponent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Token(s) => write!(f, "{s}"),
Self::ProtectedZone { builder, .. } => write!(f, "{builder}"),
Self::Space => write!(f, " "),
Self::Indent(n) => write!(f, "{}", " ".repeat(*n)),
Self::BreakLinePoint(properties) => {
write!(f, "{}", if properties.space_if_not_broken { " " } else { "" })
}
Self::Comment { content, .. } => write!(f, "{content}"),
}
}
}
#[derive(Clone, Debug)]
struct LineBuilder {
children: Vec<LineComponent>,
is_open: bool,
pending_break_line_points: Vec<LineComponent>,
}
impl fmt::Display for LineBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_only_indents() {
write!(f, "")
} else {
write!(f, "{}", self.children.iter().map(|child| child.to_string()).join(""))
}
}
}
impl LineBuilder {
pub fn default() -> Self {
Self { children: vec![], is_open: true, pending_break_line_points: vec![] }
}
pub fn new(indent_size: usize) -> Self {
if indent_size > 0 {
Self {
children: vec![LineComponent::Indent(indent_size)],
is_open: true,
pending_break_line_points: vec![],
}
} else {
Self::default()
}
}
fn open_sub_builder(&mut self, precedence: usize) {
let active_builder = self.get_active_builder_mut();
active_builder.flush_pending_break_line_points();
active_builder.push_child(LineComponent::ProtectedZone {
builder: LineBuilder::default(),
precedence,
});
}
fn close_sub_builder(&mut self) {
let active_builder = self.get_active_builder_mut();
active_builder.flush_pending_break_line_points();
active_builder.is_open = false;
}
fn push_child(&mut self, component: LineComponent) {
match &component {
LineComponent::BreakLinePoint(properties) if !properties.is_empty_line_breakpoint => {
if !self.is_only_indents() {
self.get_active_builder_mut().pending_break_line_points.push(component);
}
}
_ => {
let active_builder = self.get_active_builder_mut();
active_builder.flush_pending_break_line_points();
active_builder.children.push(component);
}
}
}
pub fn push_str(&mut self, s: &str) {
self.push_child(LineComponent::Token(s.to_string()));
}
pub fn push_space(&mut self) {
self.push_child(LineComponent::Space);
}
pub fn push_empty_line_break_line_point(&mut self) {
self.push_child(LineComponent::BreakLinePoint(BreakLinePointProperties::new_empty_line()));
}
pub fn push_break_line_point(&mut self, properties: BreakLinePointProperties) {
self.push_child(LineComponent::BreakLinePoint(properties));
}
pub fn push_comment(&mut self, content: &str, is_trailing: bool) {
let active_builder = self.get_active_builder_mut();
if let Some(LineComponent::Comment { content: prev_content, is_trailing }) =
active_builder.children.last_mut()
{
if !*is_trailing {
*prev_content += "\n";
*prev_content += content;
return;
}
}
self.push_child(LineComponent::Comment { content: content.to_string(), is_trailing });
self.push_break_line_point(BreakLinePointProperties::new(
usize::MAX,
BreakLinePointIndentation::NotIndented,
false,
false,
));
}
fn flush_pending_break_line_points(&mut self) {
self.children.append(&mut self.pending_break_line_points);
}
fn width(&self) -> usize {
self.width_between(0, self.children.len())
}
fn width_between(&self, start: usize, end: usize) -> usize {
self.children[start..end].iter().fold(0, |sum, node| sum + node.width())
}
fn get_next_break_properties(&self) -> Option<BreakLinePointProperties> {
self.children
.iter()
.filter_map(|child| {
if let LineComponent::BreakLinePoint(properties) = child {
Some(properties.clone())
} else {
None
}
})
.min()
}
fn get_break_point_indices_by_precedence(&self, precedence: usize) -> Vec<usize> {
self.children
.iter()
.enumerate()
.filter_map(|(i, child)| match child {
LineComponent::BreakLinePoint(properties)
if properties.precedence == precedence =>
{
Some(i)
}
_ => None,
})
.collect()
}
fn break_line_tree(&self, max_line_width: usize, tab_size: usize) -> Vec<String> {
let mut sub_builders = self.break_line_tree_single_level(max_line_width, tab_size);
while sub_builders.len() == 1 {
if sub_builders[0].contains_break_line_points() {
sub_builders =
sub_builders[0].break_line_tree_single_level(max_line_width, tab_size)
} else if sub_builders[0].contains_protected_zone() {
sub_builders = vec![sub_builders[0].open_protected_zone()];
} else {
return vec![self.to_string()];
}
}
sub_builders
.iter()
.flat_map(|tree| tree.break_line_tree(max_line_width, tab_size))
.collect()
}
fn break_line_tree_single_level(
&self,
max_line_width: usize,
tab_size: usize,
) -> Vec<LineBuilder> {
let Some(break_line_point_properties) = self.get_next_break_properties() else {
return vec![self.clone()];
};
let mut breaking_positions =
self.get_break_point_indices_by_precedence(break_line_point_properties.precedence);
if break_line_point_properties.is_single_breakpoint {
let mut last_break_point_index = breaking_positions.len() - 1;
let mut first_break_point_index = 0;
while first_break_point_index < last_break_point_index {
let middle_break_point_index =
(first_break_point_index + last_break_point_index + 1) / 2;
let middle_break_point = breaking_positions[middle_break_point_index];
let middle_break_point_width = self.width_between(0, middle_break_point);
if middle_break_point_width <= max_line_width {
first_break_point_index = middle_break_point_index;
} else {
last_break_point_index = middle_break_point_index - 1;
}
}
breaking_positions = vec![breaking_positions[first_break_point_index]];
}
if self.width() <= max_line_width && break_line_point_properties.is_optional {
return vec![self.remove_all_optional_break_line_points()];
}
let base_indent = self.get_leading_indent();
let mut trees: Vec<LineBuilder> = vec![];
let n_children = self.children.len();
breaking_positions.push(n_children);
let n_break_points = breaking_positions.len();
let mut current_line_start = 0;
for (i, current_line_end) in breaking_positions.iter().enumerate() {
let added_indent = match break_line_point_properties.break_indentation {
BreakLinePointIndentation::Indented if i != 0 => tab_size,
BreakLinePointIndentation::IndentedWithTail
if i != 0 && i != n_break_points - 1 =>
{
tab_size
}
_ => 0,
};
let mut comment_only_added_indent = if let BreakLinePointIndentation::IndentedWithTail =
break_line_point_properties.break_indentation
{
if i == n_break_points - 1 { tab_size } else { 0 }
} else {
0
};
let cur_indent = base_indent + added_indent;
trees.push(LineBuilder::new(cur_indent));
for j in current_line_start..*current_line_end {
match &self.children[j] {
LineComponent::Indent(_) => {}
LineComponent::Space => {
if !trees.last().unwrap().is_only_indents() {
trees.last_mut().unwrap().push_space();
}
}
LineComponent::Comment { content, is_trailing } if !is_trailing => {
trees.last_mut().unwrap().push_str(&" ".repeat(comment_only_added_indent));
let formatted_comment = format_leading_comment(
content,
cur_indent + comment_only_added_indent,
max_line_width,
);
trees.last_mut().unwrap().push_child(LineComponent::Comment {
content: formatted_comment,
is_trailing: *is_trailing,
});
}
_ => trees.last_mut().unwrap().push_child(self.children[j].clone()),
}
if !self.children[j].is_trivia() {
comment_only_added_indent = 0;
}
}
current_line_start = *current_line_end + 1;
}
trees
}
fn get_active_builder_mut(&mut self) -> &mut LineBuilder {
match self.children.last() {
Some(LineComponent::ProtectedZone { builder: sub_builder, .. })
if sub_builder.is_open => {}
_ => {
return self;
}
}
match self.children.last_mut() {
Some(LineComponent::ProtectedZone { builder: sub_builder, .. })
if sub_builder.is_open =>
{
sub_builder.get_active_builder_mut()
}
_ => {
unreachable!("This case is covered in the first match.")
}
}
}
pub fn build(&self, max_line_width: usize, tab_size: usize) -> String {
self.break_line_tree(max_line_width, tab_size).iter().join("\n") + "\n"
}
fn get_highest_protected_zone_precedence(&self) -> Option<usize> {
self.children
.iter()
.filter_map(|child| {
if let LineComponent::ProtectedZone { precedence, .. } = child {
Some(*precedence)
} else {
None
}
})
.min()
}
fn open_protected_zone(&self) -> LineBuilder {
let mut unprotected_builder = LineBuilder::default();
let mut first_protected_zone_found = false;
let highest_precedence = self
.get_highest_protected_zone_precedence()
.expect("Tried to unprotect a line builder with no protected zones.");
for child in self.children.iter() {
match child {
LineComponent::ProtectedZone { builder: sub_tree, precedence }
if *precedence == highest_precedence && !first_protected_zone_found =>
{
first_protected_zone_found = true;
for sub_child in sub_tree.children.iter() {
unprotected_builder.push_child(sub_child.clone());
}
}
_ => unprotected_builder.push_child(child.clone()),
}
}
unprotected_builder
}
fn contains_protected_zone(&self) -> bool {
self.children.iter().any(|child| matches!(child, LineComponent::ProtectedZone { .. }))
}
fn is_only_indents(&self) -> bool {
!self.children.iter().any(|child| !matches!(child, LineComponent::Indent { .. }))
}
fn get_leading_indent(&self) -> usize {
let mut leading_indent = 0;
let mut children_iter = self.children.iter();
while let Some(LineComponent::Indent(indent_size)) = children_iter.next() {
leading_indent += indent_size
}
leading_indent
}
fn contains_break_line_points(&self) -> bool {
self.children.iter().any(|child| matches!(child, LineComponent::BreakLinePoint(_)))
}
fn remove_all_optional_break_line_points(&self) -> LineBuilder {
LineBuilder {
children: self
.children
.iter()
.map(|child| match child {
LineComponent::BreakLinePoint(node_properties)
if node_properties.is_optional =>
{
LineComponent::Token(child.to_string())
}
_ => child.clone(),
})
.collect_vec(),
is_open: true,
pending_break_line_points: vec![],
}
}
}
#[derive(Clone, PartialEq, Eq)]
struct CommentLine {
n_slashes: usize,
n_exclamations: usize,
n_leading_spaces: usize,
content: String,
}
impl CommentLine {
pub fn from_string(mut comment_line: String) -> Self {
comment_line = comment_line.trim().to_string();
let n_slashes = comment_line.chars().take_while(|c| *c == '/').count();
comment_line = comment_line.chars().skip(n_slashes).collect();
let n_exclamations = comment_line.chars().take_while(|c| *c == '!').count();
comment_line = comment_line.chars().skip(n_exclamations).collect();
let n_leading_spaces = comment_line.chars().take_while(|c| *c == ' ').count();
let content = comment_line.chars().skip(n_leading_spaces).collect();
Self { n_slashes, n_exclamations, n_leading_spaces, content }
}
pub fn is_same_prefix(&self, other: &Self) -> bool {
self.n_slashes == other.n_slashes
&& self.n_exclamations == other.n_exclamations
&& self.n_leading_spaces == other.n_leading_spaces
}
pub fn is_open_line(&self) -> bool {
self.content.ends_with(|c: char| c.is_alphanumeric() || c == ',')
}
}
impl fmt::Display for CommentLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}",
"/".repeat(self.n_slashes),
"!".repeat(self.n_exclamations),
" ".repeat(self.n_leading_spaces),
self.content.trim()
)
}
}
fn format_leading_comment(content: &str, cur_indent: usize, max_line_width: usize) -> String {
let mut formatted_comment = String::new();
let mut prev_comment_line = CommentLine::from_string("".to_string());
let append_line = |formatted_comment: &mut String, comment_line: &CommentLine| {
formatted_comment.push_str(&" ".repeat(cur_indent));
formatted_comment.push_str(&comment_line.to_string());
formatted_comment.push('\n');
};
let mut last_line_broken = false;
for line in content.lines() {
let orig_comment_line = CommentLine::from_string(line.to_string());
let max_comment_width = max_line_width
- cur_indent
- orig_comment_line.n_slashes
- orig_comment_line.n_exclamations
- orig_comment_line.n_leading_spaces;
let mut current_line = if last_line_broken
&& prev_comment_line.is_open_line()
&& prev_comment_line.is_same_prefix(&orig_comment_line)
{
prev_comment_line.content += " ";
prev_comment_line
} else {
append_line(&mut formatted_comment, &prev_comment_line);
CommentLine { content: "".to_string(), ..orig_comment_line }
};
last_line_broken = false;
for word in orig_comment_line.content.split(' ') {
if current_line.content.is_empty()
|| current_line.content.len() + word.len() <= max_comment_width
{
current_line.content.push_str(word);
current_line.content.push(' ');
} else {
append_line(&mut formatted_comment, ¤t_line);
last_line_broken = true;
current_line = CommentLine { content: word.to_string(), ..current_line };
current_line.content.push(' ');
}
}
prev_comment_line = CommentLine {
n_slashes: orig_comment_line.n_slashes,
n_exclamations: orig_comment_line.n_exclamations,
n_leading_spaces: orig_comment_line.n_leading_spaces,
content: current_line.content.trim().to_string(),
};
}
append_line(&mut formatted_comment, &prev_comment_line);
formatted_comment.trim().to_string()
}
struct PendingLineState {
line_buffer: LineBuilder,
prevent_next_space: bool,
}
impl PendingLineState {
pub fn new() -> Self {
Self { line_buffer: LineBuilder::default(), prevent_next_space: true }
}
}
pub enum BreakLinePointsPositions {
Leading(BreakLinePointProperties),
Trailing(BreakLinePointProperties),
Both { leading: BreakLinePointProperties, trailing: BreakLinePointProperties },
List { properties: BreakLinePointProperties, breaking_frequency: usize },
None,
}
impl BreakLinePointsPositions {
pub fn new_symmetric(break_line_point_properties: BreakLinePointProperties) -> Self {
Self::Both {
leading: break_line_point_properties.clone(),
trailing: break_line_point_properties,
}
}
pub fn leading(&self) -> Option<BreakLinePointProperties> {
match self {
Self::Leading(properties) | Self::Both { leading: properties, .. } => {
Some(properties.clone())
}
_ => None,
}
}
pub fn trailing(&self) -> Option<BreakLinePointProperties> {
match self {
Self::Trailing(properties) | Self::Both { trailing: properties, .. } => {
Some(properties.clone())
}
_ => None,
}
}
}
pub trait SyntaxNodeFormat {
fn force_no_space_before(&self, db: &dyn SyntaxGroup) -> bool;
fn force_no_space_after(&self, db: &dyn SyntaxGroup) -> bool;
fn allow_newline_after(&self, db: &dyn SyntaxGroup) -> bool;
fn allowed_empty_between(&self, db: &dyn SyntaxGroup) -> usize;
fn get_wrapping_break_line_point_properties(
&self,
db: &dyn SyntaxGroup,
) -> BreakLinePointsPositions;
fn get_internal_break_line_point_properties(
&self,
db: &dyn SyntaxGroup,
) -> BreakLinePointsPositions;
fn get_protected_zone_precedence(&self, db: &dyn SyntaxGroup) -> Option<usize>;
fn should_skip_terminal(&self, db: &dyn SyntaxGroup) -> bool;
}
pub struct FormatterImpl<'a> {
db: &'a dyn SyntaxGroup,
config: FormatterConfig,
line_state: PendingLineState,
empty_lines_allowance: usize,
is_current_line_whitespaces: bool,
}
impl<'a> FormatterImpl<'a> {
pub fn new(db: &'a dyn SyntaxGroup, config: FormatterConfig) -> Self {
Self {
db,
config,
line_state: PendingLineState::new(),
empty_lines_allowance: 0,
is_current_line_whitespaces: true,
}
}
pub fn get_formatted_string(&mut self, syntax_node: &SyntaxNode) -> String {
self.format_node(syntax_node);
self.line_state.line_buffer.build(self.config.max_line_length, self.config.tab_size)
}
fn format_node(&mut self, syntax_node: &SyntaxNode) {
if syntax_node.text(self.db).is_some() {
panic!("Token reached before terminal.");
}
let protected_zone_precedence = syntax_node.get_protected_zone_precedence(self.db);
let node_break_points = syntax_node.get_wrapping_break_line_point_properties(self.db);
self.append_break_line_point(node_break_points.leading());
if let Some(precedence) = protected_zone_precedence {
self.line_state.line_buffer.open_sub_builder(precedence);
}
if syntax_node.force_no_space_before(self.db) {
self.line_state.prevent_next_space = true;
}
if self.should_ignore_node_format(syntax_node) {
self.line_state.line_buffer.push_str(syntax_node.get_text(self.db).trim());
} else if syntax_node.kind(self.db).is_terminal() {
self.format_terminal(syntax_node);
} else {
self.format_internal(syntax_node);
}
if syntax_node.force_no_space_after(self.db) {
self.line_state.prevent_next_space = true;
}
if protected_zone_precedence.is_some() {
self.line_state.line_buffer.close_sub_builder();
}
self.append_break_line_point(node_break_points.trailing());
}
fn format_internal(&mut self, syntax_node: &SyntaxNode) {
let allowed_empty_between = syntax_node.allowed_empty_between(self.db);
let internal_break_line_points_positions =
syntax_node.get_internal_break_line_point_properties(self.db);
let mut children = self.db.get_children(syntax_node.clone()).to_vec();
let n_children = children.len();
if self.config.sort_module_level_items {
children.sort_by_key(|c| MovableNode::new(self.db, c));
};
for (i, child) in children.iter().enumerate() {
if child.width(self.db) == TextWidth::default() {
continue;
}
self.format_node(child);
if let BreakLinePointsPositions::List { properties, breaking_frequency } =
&internal_break_line_points_positions
{
if i % breaking_frequency == breaking_frequency - 1 && i < n_children - 1 {
self.append_break_line_point(Some(properties.clone()));
}
}
self.empty_lines_allowance = allowed_empty_between;
}
}
fn format_terminal(&mut self, syntax_node: &SyntaxNode) {
let children = self.db.get_children(syntax_node.clone());
let mut children_iter = children.iter().cloned();
let leading_trivia = ast::Trivia::from_syntax_node(self.db, children_iter.next().unwrap());
let token = children_iter.next().unwrap();
let trailing_trivia = ast::Trivia::from_syntax_node(self.db, children_iter.next().unwrap());
self.format_trivia(leading_trivia, true);
if !syntax_node.should_skip_terminal(self.db) {
self.format_token(&token);
}
self.format_trivia(trailing_trivia, false);
}
fn format_trivia(&mut self, trivia: syntax::node::ast::Trivia, is_leading: bool) {
for trivium in trivia.elements(self.db) {
match trivium {
ast::Trivium::SingleLineComment(_)
| ast::Trivium::SingleLineDocComment(_)
| ast::Trivium::SingleLineInnerComment(_) => {
if !is_leading {
self.line_state.line_buffer.push_space();
}
self.line_state.line_buffer.push_comment(
&trivium.as_syntax_node().text(self.db).unwrap(),
!is_leading,
);
self.is_current_line_whitespaces = false;
self.empty_lines_allowance = 1;
}
ast::Trivium::Whitespace(_) => {}
ast::Trivium::Newline(_) => {
if self.empty_lines_allowance > 0 && self.is_current_line_whitespaces {
self.empty_lines_allowance -= 1;
self.line_state.line_buffer.push_empty_line_break_line_point();
}
self.is_current_line_whitespaces = true;
}
ast::Trivium::Skipped(_) => {
self.format_token(&trivium.as_syntax_node());
}
ast::Trivium::SkippedNode(node) => {
self.format_node(&node.as_syntax_node());
}
}
}
}
fn format_token(&mut self, syntax_node: &SyntaxNode) {
let text = syntax_node.text(self.db).unwrap();
if !syntax_node.force_no_space_before(self.db) && !self.line_state.prevent_next_space {
self.line_state.line_buffer.push_space();
}
self.line_state.prevent_next_space = syntax_node.force_no_space_after(self.db);
if syntax_node.kind(self.db) != SyntaxKind::TokenWhitespace {
self.is_current_line_whitespaces = false;
}
let node_break_points = syntax_node.get_wrapping_break_line_point_properties(self.db);
self.append_break_line_point(node_break_points.leading());
self.line_state.line_buffer.push_str(&text);
self.append_break_line_point(node_break_points.trailing());
}
fn append_break_line_point(&mut self, properties: Option<BreakLinePointProperties>) {
if let Some(properties) = properties {
self.line_state.line_buffer.push_break_line_point(properties);
self.line_state.prevent_next_space = true;
}
}
pub fn should_ignore_node_format(&self, syntax_node: &SyntaxNode) -> bool {
syntax_node.has_attr(self.db, FMT_SKIP_ATTR)
}
}
#[derive(PartialEq, Eq)]
enum MovableNode {
ItemModule(SmolStr),
ItemUse(SmolStr),
ItemHeaderDoc,
Immovable,
}
impl MovableNode {
fn new(db: &dyn SyntaxGroup, node: &SyntaxNode) -> Self {
match node.kind(db) {
SyntaxKind::ItemModule => {
let item = ast::ItemModule::from_syntax_node(db, node.clone());
if matches!(item.body(db), MaybeModuleBody::None(_)) {
Self::ItemModule(item.name(db).text(db))
} else {
Self::Immovable
}
}
SyntaxKind::ItemUse => Self::ItemUse(node.clone().get_text_without_trivia(db).into()),
SyntaxKind::ItemHeaderDoc => Self::ItemHeaderDoc,
_ => Self::Immovable,
}
}
}
impl Ord for MovableNode {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(MovableNode::ItemHeaderDoc, _) => Ordering::Less,
(_, MovableNode::ItemHeaderDoc) => Ordering::Greater,
(MovableNode::Immovable, MovableNode::Immovable) => Ordering::Equal,
(MovableNode::ItemModule(a), MovableNode::ItemModule(b))
| (MovableNode::ItemUse(a), MovableNode::ItemUse(b)) => a.cmp(b),
(_, MovableNode::Immovable) | (MovableNode::ItemModule(_), MovableNode::ItemUse(_)) => {
Ordering::Less
}
(MovableNode::Immovable, _) | (MovableNode::ItemUse(_), MovableNode::ItemModule(_)) => {
Ordering::Greater
}
}
}
}
impl PartialOrd for MovableNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}