Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions crates/analysis/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ fn type_snippet_placeholder(ty: &ValueType, n: usize) -> String {
ValueType::AsciiString | ValueType::AsciiStringList | ValueType::QuotedString => {
format!("${{{n}:Value}}")
}
ValueType::W3dModel => format!("${{{n}:Model}}"),
ValueType::W3dModel | ValueType::W3dModelList => format!("${{{n}:Model}}"),
ValueType::W3dModelMember => format!("${{{n}:Bone}}"),
_ => format!("${{{n}:?}}"),
}
Expand Down Expand Up @@ -678,7 +678,7 @@ fn completions_for_type(
}));
out
}
ValueType::W3dModel | ValueType::W3dModelMember => Vec::new(),
ValueType::W3dModel | ValueType::W3dModelList | ValueType::W3dModelMember => Vec::new(),
_ => Vec::new(),
}
}
Expand Down Expand Up @@ -791,6 +791,7 @@ fn type_label(ty: &ValueType) -> String {
ValueType::BitFlags { value_set } => format!("flags {value_set}"),
ValueType::Reference { ref_kind } => format!("ref {ref_kind:?}"),
ValueType::W3dModel => "w3d model".into(),
ValueType::W3dModelList => "w3d models".into(),
ValueType::W3dModelMember => "w3d model member".into(),
ValueType::RandomVariable { .. } => "real real [distribution]".into(),
ValueType::RandomKeyframe => "real real frame".into(),
Expand Down Expand Up @@ -986,6 +987,27 @@ End
assert!(out.contains(&"WeaponA".to_string()), "{out:?}");
}

#[test]
fn ocl_model_list_completes_every_position() {
let a = Analyzer::embedded();
let mut index = WorkspaceIndex::new();
index.set_file_models(
"models/Good.w3d",
vec![crate::index::ModelAsset {
name: "Good".into(),
members: vec![],
}],
);
let src =
"ObjectCreationList Debris\n CreateDebris\n ModelNames = First \n End\nEnd\n";
let offset = src.find("First ").unwrap() + "First ".len();
let out = complete(&a, &a.parse(src), offset as u32, Some(&index), None)
.into_iter()
.map(|item| item.label)
.collect::<Vec<_>>();
assert!(out.contains(&"Good".to_string()), "{out:?}");
}

#[test]
fn weapon_bone_completions_use_token_positions() {
let a = Analyzer::embedded();
Expand Down
35 changes: 35 additions & 0 deletions crates/analysis/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,16 @@ impl<'a> Ctx<'a> {
}
let tokens = field.value_tokens();
match &schema_field.value_type {
ValueType::W3dModelList => {
for tok in &tokens {
self.validate_model_asset_token(
&schema_field.value_type,
tok,
scope_node,
schema_field.model_source.as_ref(),
);
}
}
ValueType::TokenList { tokens: specs } => {
let mut i = 0;
for spec in specs {
Expand Down Expand Up @@ -1499,6 +1509,7 @@ impl<'a> Ctx<'a> {
| ValueType::QuotedString
| ValueType::AsciiStringList
| ValueType::W3dModel
| ValueType::W3dModelList
| ValueType::W3dModelMember
| ValueType::Color
| ValueType::Coord2D
Expand Down Expand Up @@ -2670,6 +2681,30 @@ End
);
}

#[test]
fn ocl_object_and_model_lists_validate_every_reference() {
let a = Analyzer::embedded();
let mut index = WorkspaceIndex::new();
let objects = a.parse("Object KnownObject\nEnd\n");
index.set_file(
"objects.ini",
crate::index::definitions_in(&a, &objects, "objects.ini"),
);
index.set_file_models(
"models/Good.w3d",
vec![crate::index::ModelAsset {
name: "Good".into(),
members: vec![],
}],
);
let src = "ObjectCreationList Test\n CreateObject\n ObjectNames = KnownObject MissingObject\n End\n CreateDebris\n ModelNames = Good MissingModel\n End\nEnd\n";
let diags = diagnose(&a, &a.parse(src), Some(&index), Some("ocl.ini"));
assert!(diags.iter().any(|d| d.code == "unresolved-reference"
&& &src[d.span.start as usize..d.span.end as usize] == "MissingObject"));
assert!(diags.iter().any(|d| d.code == "unknown-model"
&& &src[d.span.start as usize..d.span.end as usize] == "MissingModel"));
}

#[test]
fn model_member_strictness_supports_off_compatible_and_strict() {
let a = Analyzer::embedded();
Expand Down
15 changes: 12 additions & 3 deletions crates/analysis/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,10 +586,19 @@ fn collect_object_models(analyzer: &Analyzer, node: &SyntaxNode, out: &mut Vec<S
let Some(schema_field) = field.key().and_then(|key| scope.field(key.text())) else {
continue;
};
if matches!(schema_field.value_type, ValueType::W3dModel) {
if let Some(value) = field.value_tokens().first() {
out.push(value.text().trim_matches('"').to_string());
let values = field.value_tokens();
match schema_field.value_type {
ValueType::W3dModelList => out.extend(
values
.iter()
.map(|value| value.text().trim_matches('"').to_string()),
),
ValueType::W3dModel => {
if let Some(value) = values.first() {
out.push(value.text().trim_matches('"').to_string());
}
}
_ => {}
}
}
SyntaxKind::BLOCK | SyntaxKind::MODULE => collect_object_models(analyzer, &child, out),
Expand Down
7 changes: 6 additions & 1 deletion crates/analysis/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ pub fn enclosing_scopes<'a>(analyzer: &'a Analyzer, node: &SyntaxNode) -> Vec<Sc
}

pub(crate) fn is_model_asset_type(ty: &ValueType) -> bool {
matches!(ty, ValueType::W3dModel)
matches!(ty, ValueType::W3dModel | ValueType::W3dModelList)
}

pub(crate) fn is_model_member_type(ty: &ValueType) -> bool {
Expand Down Expand Up @@ -253,6 +253,11 @@ fn collect_models(analyzer: &Analyzer, node: &SyntaxNode, out: &mut Vec<String>)
.map(|value| value.text().trim_matches('"'))
.collect::<Vec<_>>();
match &schema_field.value_type {
ValueType::W3dModelList => out.extend(
values
.iter()
.map(|value| value.text().trim_matches('"').to_string()),
),
ValueType::TokenList { .. }
| ValueType::OneOf { .. }
| ValueType::Prefixed { .. } => {
Expand Down
1 change: 1 addition & 0 deletions crates/analysis/src/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ fn value_token_kind(tok: &SyntaxToken, ty: Option<&ValueType>) -> SemKind {
Some(ValueType::Reference { .. })
| Some(ValueType::ReferenceList { .. })
| Some(ValueType::W3dModel)
| Some(ValueType::W3dModelList)
| Some(ValueType::W3dModelMember) => SemKind::Reference,
_ => SemKind::StringLit,
}
Expand Down
22 changes: 22 additions & 0 deletions crates/analysis/tests/spec/OCLReferences.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Object OCLObjectOne
End

Object OCLObjectTwo
End

ParticleSystem HulkExplosionTrail
End

AudioEvent DebrisBigMetal
End

ObjectCreationList OCL_References
CreateObject
ObjectNames = OCLObjectOne $1NoSuchOCLObject
ParticleSystem = $2NoSuchParticleSystem
End
CreateDebris
ParticleSystem = $3NoSuchDebrisParticleSystem
BounceSound = $4NoSuchBounceSound
End
End
37 changes: 37 additions & 0 deletions crates/analysis/tests/spec/OCLReferences.spec.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ObjectNames is a variadic list of Object references.

[[diag]]
severity = "warning"
code = "unresolved-reference"
on = "NoSuchOCLObject"

[[complete]]
at = "$1"
includes = ["OCLObjectOne", "OCLObjectTwo"]

[[diag]]
severity = "warning"
code = "unresolved-reference"
on = "NoSuchParticleSystem"

[[diag]]
severity = "warning"
code = "unresolved-reference"
on = "NoSuchDebrisParticleSystem"

[[diag]]
severity = "warning"
code = "unresolved-reference"
on = "NoSuchBounceSound"

[[complete]]
at = "$2"
includes = ["HulkExplosionTrail"]

[[complete]]
at = "$3"
includes = ["HulkExplosionTrail"]

[[complete]]
at = "$4"
includes = ["DebrisBigMetal"]
17 changes: 9 additions & 8 deletions crates/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12544,7 +12544,8 @@
{
"name": "ParticleSystem",
"value_type": {
"kind": "ascii_string"
"kind": "reference",
"ref_kind": "particle_system"
},
"parse_fn": "INI::parseAsciiString"
},
Expand Down Expand Up @@ -12756,8 +12757,8 @@
{
"name": "ObjectNames",
"value_type": {
"kind": "unknown",
"parse_fn": "parseDebrisObjectNames"
"kind": "reference_list",
"ref_kind": "object"
},
"parse_fn": "parseDebrisObjectNames"
},
Expand Down Expand Up @@ -12825,7 +12826,8 @@
{
"name": "ParticleSystem",
"value_type": {
"kind": "ascii_string"
"kind": "reference",
"ref_kind": "particle_system"
},
"parse_fn": "INI::parseAsciiString"
},
Expand Down Expand Up @@ -13030,8 +13032,7 @@
{
"name": "ModelNames",
"value_type": {
"kind": "unknown",
"parse_fn": "parseDebrisObjectNames"
"kind": "w3d_model_list"
},
"parse_fn": "parseDebrisObjectNames"
},
Expand Down Expand Up @@ -13084,8 +13085,8 @@
{
"name": "BounceSound",
"value_type": {
"kind": "unknown",
"parse_fn": "INI::parseAudioEventRTS"
"kind": "reference",
"ref_kind": "audio_event"
},
"parse_fn": "INI::parseAudioEventRTS"
}
Expand Down
2 changes: 2 additions & 0 deletions crates/schema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ pub enum ValueType {
AsciiStringList,
/// A W3D model asset name, backed by indexed `.w3d` files.
W3dModel,
/// One or more W3D model asset names.
W3dModelList,
/// A bone, subobject, mesh, or other member of a W3D model asset.
W3dModelMember,
/// `R:r G:g B:b [A:a]` color.
Expand Down