use ropey::Rope;
use tower_lsp_server::lsp_types::*;
use xmlparser::{ElementEnd, Token, Tokenizer};
use crate::index::{ModuleName, PathSymbol, RecordId, _I, _R};
use crate::model::ModelName;
use crate::utils::{offset_to_position, position_to_offset};
use crate::utils::{ByteOffset, MinLoc};
use crate::{errloc, some, ImStr};
#[derive(Debug)]
pub struct Record {
pub deleted: bool,
pub id: ImStr,
pub module: ModuleName,
pub model: Option<ModelName>,
pub inherit_id: Option<RecordId>,
pub location: MinLoc,
}
impl Record {
pub fn qualified_id(&self) -> String {
format!("{}.{}", _R(self.module), self.id)
}
pub fn from_reader(
offset: ByteOffset,
module: ModuleName,
path: PathSymbol,
reader: &mut Tokenizer,
rope: Rope,
) -> anyhow::Result<Option<Self>> {
let mut id = None;
let mut model = None;
let mut inherit_id = None;
let mut end = None;
let mut stack = 1;
let mut in_record = true;
let start = offset_to_position(offset, rope.clone()).ok_or_else(|| errloc!("{}", path))?;
loop {
match reader.next() {
Some(Ok(Token::Attribute { local, value, .. })) if stack == 1 && in_record => match local.as_str() {
"id" => {
if let Some((_, xml_id)) = value.split_once('.') {
id = Some(xml_id.into());
} else {
id = Some(value.as_str().into());
}
}
"model" => model = Some(_I(value.as_str()).into()),
_ => {}
},
Some(Ok(Token::ElementStart { local, .. })) => {
in_record = local.as_str() == "record";
if in_record {
stack += 1;
}
if local.as_str() == "field" {
let Some(maybe_inherit_id) = extract_inherit_id(reader, stack) else {
continue;
};
if maybe_inherit_id.contains('.') {
inherit_id = Some(_I(maybe_inherit_id).into());
} else {
inherit_id = Some(_I(format!("{}.{maybe_inherit_id}", _R(module))).into());
}
}
}
Some(Ok(Token::ElementEnd {
end: ElementEnd::Close(_, local),
span,
..
})) if local.as_str() == "record" => {
stack -= 1;
if stack <= 0 {
end = Some(ByteOffset(span.end()));
break;
}
}
Some(Ok(Token::ElementEnd {
end: ElementEnd::Empty,
span,
..
})) if in_record => {
stack -= 1;
if stack <= 0 {
end = Some(ByteOffset(span.end()));
break;
}
}
None => break,
Some(Err(err)) => {
let pos = Position {
line: err.pos().row - 1,
character: err.pos().col - 1,
};
end = position_to_offset(pos, &rope);
break;
}
_ => {}
}
}
let id = some!(id);
let end = end.ok_or_else(|| errloc!("Unbound range for record"))?;
let end = offset_to_position(end, rope.clone()).ok_or_else(|| errloc!("{}", path))?;
let range = Range { start, end };
Ok(Some(Self {
deleted: false,
id,
module,
model,
inherit_id,
location: MinLoc { path, range },
}))
}
pub fn template(
offset: ByteOffset,
module: ModuleName,
path: PathSymbol,
reader: &mut Tokenizer,
rope: Rope,
) -> anyhow::Result<Option<Self>> {
let start = offset_to_position(offset, rope.clone()).ok_or_else(|| errloc!("{}", path))?;
let mut id = None;
let mut inherit_id = None;
let mut end = None;
let mut stack = 1;
let mut in_template = true;
loop {
match reader.next() {
Some(Ok(Token::Attribute { local, value, .. })) if in_template => match local.as_str() {
"id" => {
if let Some((_, xml_id)) = value.split_once('.') {
id = Some(xml_id.into());
} else {
id = Some(value.as_str().into());
}
}
"inherit_id" => {
if value.contains('.') {
inherit_id = Some(_I(value.as_str()).into());
} else {
inherit_id = Some(_I(format!("{}.{value}", _R(module))).into())
}
}
_ => {}
},
Some(Ok(Token::ElementEnd {
end: ElementEnd::Empty,
span,
..
})) if in_template => {
stack -= 1;
if stack <= 0 {
end = Some(ByteOffset(span.end()));
break;
}
}
Some(Ok(Token::ElementEnd {
end: ElementEnd::Open, ..
})) if in_template && stack == 1 && id.is_none() => {
return Ok(None);
}
Some(Ok(Token::ElementEnd {
end: ElementEnd::Close(_, local),
span,
..
})) if local.as_str() == "template" => {
stack -= 1;
if stack <= 0 {
end = Some(ByteOffset(span.end()));
break;
}
}
Some(Ok(Token::ElementStart { local, .. })) => {
in_template = local.as_str() == "template";
if in_template {
stack += 1
}
}
None => break,
Some(Err(err)) => {
let pos = Position {
line: err.pos().row - 1,
character: err.pos().col - 1,
};
end = position_to_offset(pos, &rope);
break;
}
_ => {}
}
}
let end = end.ok_or_else(|| errloc!("Unbound range for template"))?;
let end = offset_to_position(end, rope).ok_or_else(|| errloc!("{}", path))?;
let range = Range { start, end };
Ok(Some(Self {
id: some!(id),
deleted: false,
model: Some(_I("ir.ui.view").into()),
module,
inherit_id,
location: MinLoc { path, range },
}))
}
pub fn menuitem(
offset: ByteOffset,
module: ModuleName,
path: PathSymbol,
reader: &mut Tokenizer,
rope: Rope,
) -> anyhow::Result<Option<Self>> {
let mut id = None;
let mut end = None;
let start = offset_to_position(offset, rope.clone()).ok_or_else(|| errloc!("{}", path))?;
loop {
match reader.next() {
Some(Ok(Token::Attribute { local, value, .. })) if local.as_str() == "id" => {
if let Some((_, xml_id)) = value.split_once('.') {
id = Some(xml_id.into());
} else {
id = Some(value.as_str().into());
}
}
Some(Ok(Token::ElementEnd { span, .. })) => {
end = Some(ByteOffset(span.end()));
break;
}
None => break,
Some(Err(err)) => {
let pos = Position {
line: err.pos().row - 1,
character: err.pos().col - 1,
};
end = position_to_offset(pos, &rope);
break;
}
_ => {}
}
}
let id = some!(id);
let end = end.ok_or_else(|| errloc!("Unbound range for menuitem"))?;
let end = offset_to_position(end, rope).ok_or_else(|| errloc!("{}", path))?;
let range = Range { start, end };
Ok(Some(Self {
id,
deleted: false,
model: Some(_I("ir.ui.menu").into()),
module,
inherit_id: None,
location: MinLoc { path, range },
}))
}
}
fn extract_inherit_id<'text>(reader: &mut Tokenizer<'text>, stack: i32) -> Option<&'text str> {
let mut is_inherit_id = false;
let mut maybe_inherit_id = None;
loop {
match reader.next() {
Some(Ok(Token::Attribute { local, value, .. }))
if local.as_str() == "name" && value.as_str() == "inherit_id" =>
{
is_inherit_id = true
}
Some(Ok(Token::Attribute { local, value, .. })) if local.as_str() == "ref" => {
maybe_inherit_id = Some(value.as_str());
}
Some(Ok(Token::ElementEnd { .. })) => break,
None | Some(Err(_)) => break,
_ => {}
}
}
if !is_inherit_id || stack > 1 {
return None;
}
maybe_inherit_id
}