use std::collections::{BTreeSet, HashSet};
use std::ops::Range;
use tree_sitter::{Node, Tree};
use crate::ast::Language;
use crate::code_index::state::IndexState;
use super::text::Edit;
use super::{python, rust, script};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Family {
Rust,
Script,
Python,
}
impl Family {
pub(crate) fn of(language: Language) -> Option<Self> {
match language {
Language::Rust => Some(Self::Rust),
Language::TypeScript | Language::Tsx | Language::JavaScript | Language::Jsx => {
Some(Self::Script)
}
Language::Python => Some(Self::Python),
_ => None,
}
}
pub(crate) fn item_gap(self) -> usize {
match self {
Self::Python => 3,
_ => 2,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) enum ModuleRef {
Rust {
crate_dir: String,
path: Vec<String>,
},
Script(String),
Python(String),
External(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) enum Shape {
Named,
Default,
Namespace,
Glob,
}
#[derive(Clone, Debug)]
pub(crate) struct Binding {
pub local: String,
pub imported: String,
pub target: ModuleRef,
pub shape: Shape,
pub type_only: bool,
pub reexport: bool,
pub prefix: String,
pub top_level: bool,
pub removal: Range<usize>,
pub statement: Range<usize>,
pub sole: bool,
}
#[derive(Clone, Debug)]
pub(crate) struct ListStatement {
pub target: ModuleRef,
pub type_only: bool,
pub reexport: bool,
pub prefix: String,
pub append_at: usize,
}
#[derive(Clone, Debug)]
pub(crate) struct TopItem {
pub name: String,
pub range: Range<usize>,
pub exported: bool,
pub type_like: bool,
pub movable: bool,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct FileModel {
pub bindings: Vec<Binding>,
pub lists: Vec<ListStatement>,
pub items: Vec<TopItem>,
pub insert_at: usize,
pub has_imports: bool,
pub import_ranges: Vec<Range<usize>>,
}
impl FileModel {
pub(crate) fn item(&self, name: &str) -> Option<&TopItem> {
self.items.iter().find(|item| item.name == name)
}
pub(crate) fn binding_of(&self, local: &str) -> Option<&Binding> {
self.bindings
.iter()
.find(|b| b.local == local && b.shape != Shape::Glob)
}
pub(crate) fn in_import(&self, at: usize) -> bool {
self.import_ranges.iter().any(|r| r.contains(&at))
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct ImportRequest {
pub target: ModuleRef,
pub shape: Shape,
pub imported: String,
pub local: String,
pub type_only: bool,
pub reexport: bool,
pub prefix: String,
pub anchor: Option<(usize, usize)>,
}
impl ImportRequest {
pub(crate) fn named(target: ModuleRef, name: &str) -> Self {
Self {
target,
shape: Shape::Named,
imported: name.to_string(),
local: name.to_string(),
type_only: false,
reexport: false,
prefix: String::new(),
anchor: None,
}
}
pub(crate) fn satisfied_by(&self, binding: &Binding) -> bool {
binding.target == self.target
&& binding.shape == self.shape
&& binding.imported == self.imported
&& binding.local == self.local
&& binding.reexport == self.reexport
}
}
pub(crate) struct QualifiedRewrite {
pub edit: Edit,
pub import: Option<ImportRequest>,
}
pub(crate) enum Lang {
Rust(rust::Workspace),
Script(script::Workspace),
Python(python::Workspace),
}
impl Lang {
pub(crate) fn new(family: Family, state: &IndexState, extra: &[&str]) -> Self {
let mut files: HashSet<String> = state
.files
.values()
.map(|f| f.relative_path.clone())
.collect();
files.extend(extra.iter().map(|p| p.to_string()));
match family {
Family::Rust => Self::Rust(rust::Workspace::new(state.root.clone())),
Family::Script => Self::Script(script::Workspace::new(files)),
Family::Python => Self::Python(python::Workspace::new(files)),
}
}
pub(crate) fn module_of(&mut self, path: &str) -> Option<ModuleRef> {
match self {
Self::Rust(ws) => ws.module_of(path),
Self::Script(_) => Some(ModuleRef::Script(path.to_string())),
Self::Python(ws) => Some(ModuleRef::Python(ws.module_of(path))),
}
}
pub(crate) fn model(&mut self, path: &str, tree: &Tree, source: &str) -> FileModel {
match self {
Self::Rust(ws) => ws.model(path, tree, source),
Self::Script(ws) => ws.model(path, tree, source),
Self::Python(ws) => ws.model(path, tree, source),
}
}
pub(crate) fn render(
&mut self,
path: &str,
source: &str,
req: &ImportRequest,
) -> Result<String, String> {
match self {
Self::Rust(ws) => ws.render(path, req),
Self::Script(ws) => Ok(ws.render(path, source, req)),
Self::Python(_) => Ok(python::render(req)),
}
}
pub(crate) fn merge_text(&self, list: &ListStatement, req: &ImportRequest) -> String {
let alias = if req.local != req.imported {
format!(" as {}", req.local)
} else {
String::new()
};
let kind = if req.type_only && !list.type_only && matches!(self, Self::Script(_)) {
"type "
} else {
""
};
format!(", {kind}{}{alias}", req.imported)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn qualified(
&mut self,
path: &str,
source: &str,
model: &FileModel,
node: Node<'_>,
from: &ModuleRef,
to: &ModuleRef,
bare: bool,
) -> Result<Option<QualifiedRewrite>, String> {
match self {
Self::Rust(ws) => ws.qualified(path, source, model, node, from, to, bare),
Self::Script(_) => Ok(script::qualified(source, model, node, from, to, bare)),
Self::Python(_) => Ok(python::qualified(source, model, node, from, to, bare)),
}
}
pub(crate) fn free_names(
&self,
item: Node<'_>,
source: &str,
name: &str,
) -> (BTreeSet<String>, BTreeSet<String>) {
match self {
Self::Rust(_) => rust::free_names(item, source, name),
Self::Script(_) => script::free_names(item, source, name),
Self::Python(_) => python::free_names(item, source, name),
}
}
pub(crate) fn insert_separator(&self) -> usize {
match self {
Self::Python(_) => 2,
_ => 1,
}
}
}