Fix confusing keymap json errors and hovers for nonexistent actions (#23098)

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-01-13 14:53:12 -07:00 committed by GitHub
parent c599ba64bc
commit ae103fdf64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,10 +1,10 @@
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
use anyhow::{anyhow, Context, Result};
use collections::{BTreeMap, HashMap};
use gpui::{Action, AppContext, KeyBinding, SharedString};
use gpui::{Action, AppContext, KeyBinding, NoAction, SharedString};
use schemars::{
gen::{SchemaGenerator, SchemaSettings},
schema::{ArrayValidation, InstanceType, Metadata, Schema, SchemaObject, SubschemaValidation},
schema::{ArrayValidation, InstanceType, Schema, SchemaObject, SubschemaValidation},
JsonSchema,
};
use serde::Deserialize;
@ -161,21 +161,68 @@ impl KeymapFile {
Some(input.into())
}
fn add_deprecation_notice(schema_object: &mut SchemaObject, new_name: &SharedString) {
fn add_deprecation(schema_object: &mut SchemaObject, message: String) {
schema_object.extensions.insert(
// deprecationMessage is not part of the JSON Schema spec,
// but json-language-server recognizes it.
"deprecationMessage".to_owned(),
format!("Deprecated, use {new_name}").into(),
Value::String(message),
);
}
fn add_deprecation_preferred_name(schema_object: &mut SchemaObject, new_name: &str) {
add_deprecation(schema_object, format!("Deprecated, use {new_name}"));
}
fn add_description(schema_object: &mut SchemaObject, description: String) {
schema_object
.metadata
.get_or_insert(Default::default())
.description = Some(description);
}
let empty_object: SchemaObject = SchemaObject {
instance_type: set(InstanceType::Object),
..Default::default()
};
let mut keymap_action_alternatives = Vec::new();
// This is a workaround for a json-language-server issue where it matches the first
// alternative that matches the value's shape and uses that for documentation.
//
// In the case of the array validations, it would even provide an error saying that the name
// must match the name of the first alternative.
let mut plain_action = SchemaObject {
instance_type: set(InstanceType::String),
const_value: Some(Value::String("".to_owned())),
..Default::default()
};
let no_action_message = "No action named this.";
add_description(&mut plain_action, no_action_message.to_owned());
add_deprecation(&mut plain_action, no_action_message.to_owned());
let mut matches_action_name = SchemaObject {
const_value: Some(Value::String("".to_owned())),
..Default::default()
};
let no_action_message = "No action named this that takes input.";
add_description(&mut matches_action_name, no_action_message.to_owned());
add_deprecation(&mut matches_action_name, no_action_message.to_owned());
let action_with_input = SchemaObject {
instance_type: set(InstanceType::Array),
array: set(ArrayValidation {
items: set(vec![
matches_action_name.into(),
// Accept any value, as we want this to be the preferred match when there is a
// typo in the name.
Schema::Bool(true),
]),
min_items: Some(2),
max_items: Some(2),
..Default::default()
}),
..Default::default()
};
let mut keymap_action_alternatives = vec![plain_action.into(), action_with_input.into()];
for (name, action_schema) in action_schemas.iter() {
let schema = if let Some(Schema::Object(schema)) = action_schema {
Some(schema.clone())
@ -183,60 +230,61 @@ impl KeymapFile {
None
};
// If the type has a description, also apply it to the value. Ideally it would be
// removed and applied to the overall array, but `json-language-server` does not show
// these descriptions.
let description = schema.as_ref().and_then(|schema| {
schema
.metadata
.as_ref()
.and_then(|metadata| metadata.description.as_ref())
.and_then(|metadata| metadata.description.clone())
});
let mut matches_action_name = SchemaObject {
const_value: Some(Value::String(name.to_string())),
..Default::default()
let deprecation = if name == NoAction.name() {
Some("null")
} else {
deprecations.get(name).map(|new_name| new_name.as_ref())
};
if let Some(description) = description {
matches_action_name.metadata = set(Metadata {
description: Some(description.clone()),
..Default::default()
});
}
// Add an alternative for plain action names.
let deprecation = deprecations.get(name);
let mut plain_action = SchemaObject {
instance_type: set(InstanceType::String),
const_value: Some(Value::String(name.to_string())),
..Default::default()
};
if let Some(new_name) = deprecation {
add_deprecation_notice(&mut plain_action, new_name);
add_deprecation_preferred_name(&mut plain_action, new_name);
}
if let Some(description) = description.clone() {
add_description(&mut plain_action, description);
}
keymap_action_alternatives.push(plain_action.into());
// When all fields are skipped or an empty struct is added with impl_actions! /
// impl_actions_as! an empty struct is produced. The action should be invoked without
// data in this case.
// Add an alternative for actions with data specified as a [name, data] array.
//
// When a struct with no deserializable fields is added with impl_actions! /
// impl_actions_as! an empty object schema is produced. The action should be invoked
// without data in this case.
if let Some(schema) = schema {
if schema != empty_object {
let mut action_with_data = SchemaObject {
instance_type: set(InstanceType::Array),
array: Some(
ArrayValidation {
items: set(vec![matches_action_name.into(), schema.into()]),
min_items: Some(2),
max_items: Some(2),
..Default::default()
}
.into(),
),
let mut matches_action_name = SchemaObject {
const_value: Some(Value::String(name.to_string())),
..Default::default()
};
if let Some(new_name) = deprecation {
add_deprecation_notice(&mut action_with_data, new_name);
if let Some(description) = description.clone() {
add_description(&mut matches_action_name, description.to_string());
}
keymap_action_alternatives.push(action_with_data.into());
if let Some(new_name) = deprecation {
add_deprecation_preferred_name(&mut matches_action_name, new_name);
}
let action_with_input = SchemaObject {
instance_type: set(InstanceType::Array),
array: set(ArrayValidation {
items: set(vec![matches_action_name.into(), schema.into()]),
min_items: Some(2),
max_items: Some(2),
..Default::default()
}),
..Default::default()
};
keymap_action_alternatives.push(action_with_input.into());
}
}
}