mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Next up : generating configs for workspace level configs
This commit is contained in:
parent
d51fd9f196
commit
94ed6217dd
6 changed files with 48 additions and 110 deletions
|
@ -294,18 +294,6 @@ config_data! {
|
||||||
/// This option does not take effect until rust-analyzer is restarted.
|
/// This option does not take effect until rust-analyzer is restarted.
|
||||||
rustc_source: Option<String> = None,
|
rustc_source: Option<String> = None,
|
||||||
|
|
||||||
/// Additional arguments to `rustfmt`.
|
|
||||||
rustfmt_extraArgs: Vec<String> = vec![],
|
|
||||||
/// Advanced option, fully override the command rust-analyzer uses for
|
|
||||||
/// formatting. This should be the equivalent of `rustfmt` here, and
|
|
||||||
/// not that of `cargo fmt`. The file contents will be passed on the
|
|
||||||
/// standard input and the formatted result will be read from the
|
|
||||||
/// standard output.
|
|
||||||
rustfmt_overrideCommand: Option<Vec<String>> = None,
|
|
||||||
/// Enables the use of rustfmt's unstable range formatting command for the
|
|
||||||
/// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
|
|
||||||
/// available on a nightly build.
|
|
||||||
rustfmt_rangeFormatting_enable: bool = false,
|
|
||||||
|
|
||||||
/// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].
|
/// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].
|
||||||
///
|
///
|
||||||
|
@ -439,6 +427,18 @@ config_data! {
|
||||||
config_data! {
|
config_data! {
|
||||||
workspace: struct WorkspaceDefaultConfigData <- WorkspaceConfigInput -> {
|
workspace: struct WorkspaceDefaultConfigData <- WorkspaceConfigInput -> {
|
||||||
|
|
||||||
|
/// Additional arguments to `rustfmt`.
|
||||||
|
rustfmt_extraArgs: Vec<String> = vec![],
|
||||||
|
/// Advanced option, fully override the command rust-analyzer uses for
|
||||||
|
/// formatting. This should be the equivalent of `rustfmt` here, and
|
||||||
|
/// not that of `cargo fmt`. The file contents will be passed on the
|
||||||
|
/// standard input and the formatted result will be read from the
|
||||||
|
/// standard output.
|
||||||
|
rustfmt_overrideCommand: Option<Vec<String>> = None,
|
||||||
|
/// Enables the use of rustfmt's unstable range formatting command for the
|
||||||
|
/// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
|
||||||
|
/// available on a nightly build.
|
||||||
|
rustfmt_rangeFormatting_enable: bool = false,
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -775,7 +775,7 @@ pub struct Config {
|
||||||
user_config_path: VfsPath,
|
user_config_path: VfsPath,
|
||||||
|
|
||||||
/// Config node whose values apply to **every** Rust project.
|
/// Config node whose values apply to **every** Rust project.
|
||||||
user_config: Option<(GlobalLocalConfigInput, ConfigErrors)>,
|
user_config: Option<(GlobalWorkspaceLocalConfigInput, ConfigErrors)>,
|
||||||
|
|
||||||
ratoml_file: FxHashMap<SourceRootId, (RatomlFile, ConfigErrors)>,
|
ratoml_file: FxHashMap<SourceRootId, (RatomlFile, ConfigErrors)>,
|
||||||
|
|
||||||
|
@ -825,13 +825,13 @@ impl Config {
|
||||||
if let Ok(table) = toml::from_str(&change) {
|
if let Ok(table) = toml::from_str(&change) {
|
||||||
let mut toml_errors = vec![];
|
let mut toml_errors = vec![];
|
||||||
validate_toml_table(
|
validate_toml_table(
|
||||||
GlobalLocalConfigInput::FIELDS,
|
GlobalWorkspaceLocalConfigInput::FIELDS,
|
||||||
&table,
|
&table,
|
||||||
&mut String::new(),
|
&mut String::new(),
|
||||||
&mut toml_errors,
|
&mut toml_errors,
|
||||||
);
|
);
|
||||||
config.user_config = Some((
|
config.user_config = Some((
|
||||||
GlobalLocalConfigInput::from_toml(table, &mut toml_errors),
|
GlobalWorkspaceLocalConfigInput::from_toml(table, &mut toml_errors),
|
||||||
ConfigErrors(
|
ConfigErrors(
|
||||||
toml_errors
|
toml_errors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -960,7 +960,7 @@ impl Config {
|
||||||
match toml::from_str(&text) {
|
match toml::from_str(&text) {
|
||||||
Ok(table) => {
|
Ok(table) => {
|
||||||
validate_toml_table(
|
validate_toml_table(
|
||||||
GlobalLocalConfigInput::FIELDS,
|
WorkspaceLocalConfigInput::FIELDS,
|
||||||
&table,
|
&table,
|
||||||
&mut String::new(),
|
&mut String::new(),
|
||||||
&mut toml_errors,
|
&mut toml_errors,
|
||||||
|
@ -1863,16 +1863,16 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rustfmt(&self) -> RustfmtConfig {
|
pub fn rustfmt(&self, source_root_id: Option<SourceRootId>) -> RustfmtConfig {
|
||||||
match &self.rustfmt_overrideCommand() {
|
match &self.rustfmt_overrideCommand(source_root_id) {
|
||||||
Some(args) if !args.is_empty() => {
|
Some(args) if !args.is_empty() => {
|
||||||
let mut args = args.clone();
|
let mut args = args.clone();
|
||||||
let command = args.remove(0);
|
let command = args.remove(0);
|
||||||
RustfmtConfig::CustomCommand { command, args }
|
RustfmtConfig::CustomCommand { command, args }
|
||||||
}
|
}
|
||||||
Some(_) | None => RustfmtConfig::Rustfmt {
|
Some(_) | None => RustfmtConfig::Rustfmt {
|
||||||
extra_args: self.rustfmt_extraArgs().clone(),
|
extra_args: self.rustfmt_extraArgs(source_root_id).clone(),
|
||||||
enable_range_formatting: *self.rustfmt_rangeFormatting_enable(),
|
enable_range_formatting: *self.rustfmt_rangeFormatting_enable(source_root_id),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2555,24 +2555,20 @@ macro_rules! _impl_for_config_data {
|
||||||
$vis fn $field(&self, source_root: Option<SourceRootId>) -> &$ty {
|
$vis fn $field(&self, source_root: Option<SourceRootId>) -> &$ty {
|
||||||
let mut source_root = source_root.as_ref();
|
let mut source_root = source_root.as_ref();
|
||||||
while let Some(sr) = source_root {
|
while let Some(sr) = source_root {
|
||||||
if let Some((file, _)) = self.ratoml_file.get(&sr) {
|
if let Some((RatomlFile::Workspace(config), _)) = self.ratoml_file.get(&sr) {
|
||||||
match file {
|
|
||||||
RatomlFile::Workspace(config) => {
|
|
||||||
if let Some(v) = config.workspace.$field.as_ref() {
|
if let Some(v) = config.workspace.$field.as_ref() {
|
||||||
return &v;
|
return &v;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
source_root = self.source_root_parent_map.get(&sr);
|
source_root = self.source_root_parent_map.get(&sr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(v) = self.client_config.0.local.$field.as_ref() {
|
if let Some(v) = self.client_config.0.workspace.$field.as_ref() {
|
||||||
return &v;
|
return &v;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((user_config, _)) = self.user_config.as_ref() {
|
if let Some((user_config, _)) = self.user_config.as_ref() {
|
||||||
if let Some(v) = user_config.local.$field.as_ref() {
|
if let Some(v) = user_config.workspace.$field.as_ref() {
|
||||||
return &v;
|
return &v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2591,7 +2587,6 @@ macro_rules! _impl_for_config_data {
|
||||||
$(
|
$(
|
||||||
$($doc)*
|
$($doc)*
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
// TODO Remove source_root
|
|
||||||
$vis fn $field(&self) -> &$ty {
|
$vis fn $field(&self) -> &$ty {
|
||||||
if let Some(v) = self.client_config.0.global.$field.as_ref() {
|
if let Some(v) = self.client_config.0.global.$field.as_ref() {
|
||||||
return &v;
|
return &v;
|
||||||
|
@ -2730,6 +2725,7 @@ use _config_data as config_data;
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
struct DefaultConfigData {
|
struct DefaultConfigData {
|
||||||
global: GlobalDefaultConfigData,
|
global: GlobalDefaultConfigData,
|
||||||
|
workspace: WorkspaceDefaultConfigData,
|
||||||
local: LocalDefaultConfigData,
|
local: LocalDefaultConfigData,
|
||||||
client: ClientDefaultConfigData,
|
client: ClientDefaultConfigData,
|
||||||
}
|
}
|
||||||
|
@ -2740,6 +2736,7 @@ struct DefaultConfigData {
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
struct FullConfigInput {
|
struct FullConfigInput {
|
||||||
global: GlobalConfigInput,
|
global: GlobalConfigInput,
|
||||||
|
workspace: WorkspaceConfigInput,
|
||||||
local: LocalConfigInput,
|
local: LocalConfigInput,
|
||||||
client: ClientConfigInput,
|
client: ClientConfigInput,
|
||||||
}
|
}
|
||||||
|
@ -2753,6 +2750,7 @@ impl FullConfigInput {
|
||||||
global: GlobalConfigInput::from_json(&mut json, error_sink),
|
global: GlobalConfigInput::from_json(&mut json, error_sink),
|
||||||
local: LocalConfigInput::from_json(&mut json, error_sink),
|
local: LocalConfigInput::from_json(&mut json, error_sink),
|
||||||
client: ClientConfigInput::from_json(&mut json, error_sink),
|
client: ClientConfigInput::from_json(&mut json, error_sink),
|
||||||
|
workspace: WorkspaceConfigInput::from_json(&mut json, error_sink),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2783,26 +2781,28 @@ impl FullConfigInput {
|
||||||
/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
|
/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
|
||||||
/// all fields being None.
|
/// all fields being None.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
struct GlobalLocalConfigInput {
|
struct GlobalWorkspaceLocalConfigInput {
|
||||||
global: GlobalConfigInput,
|
global: GlobalConfigInput,
|
||||||
local: LocalConfigInput,
|
local: LocalConfigInput,
|
||||||
|
workspace: WorkspaceConfigInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalLocalConfigInput {
|
impl GlobalWorkspaceLocalConfigInput {
|
||||||
const FIELDS: &'static [&'static [&'static str]] =
|
const FIELDS: &'static [&'static [&'static str]] =
|
||||||
&[GlobalConfigInput::FIELDS, LocalConfigInput::FIELDS];
|
&[GlobalConfigInput::FIELDS, LocalConfigInput::FIELDS];
|
||||||
fn from_toml(
|
fn from_toml(
|
||||||
toml: toml::Table,
|
toml: toml::Table,
|
||||||
error_sink: &mut Vec<(String, toml::de::Error)>,
|
error_sink: &mut Vec<(String, toml::de::Error)>,
|
||||||
) -> GlobalLocalConfigInput {
|
) -> GlobalWorkspaceLocalConfigInput {
|
||||||
GlobalLocalConfigInput {
|
GlobalWorkspaceLocalConfigInput {
|
||||||
global: GlobalConfigInput::from_toml(&toml, error_sink),
|
global: GlobalConfigInput::from_toml(&toml, error_sink),
|
||||||
local: LocalConfigInput::from_toml(&toml, error_sink),
|
local: LocalConfigInput::from_toml(&toml, error_sink),
|
||||||
|
workspace: WorkspaceConfigInput::from_toml(&toml, error_sink),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All of the config levels, all fields `Option<T>`, to describe fields that are actually set by
|
/// Workspace and local config levels, all fields `Option<T>`, to describe fields that are actually set by
|
||||||
/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
|
/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
|
||||||
/// all fields being None.
|
/// all fields being None.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
|
@ -2113,8 +2113,9 @@ fn run_rustfmt(
|
||||||
let edition = editions.iter().copied().max();
|
let edition = editions.iter().copied().max();
|
||||||
|
|
||||||
let line_index = snap.file_line_index(file_id)?;
|
let line_index = snap.file_line_index(file_id)?;
|
||||||
|
let source_root_id = snap.analysis.source_root_id(file_id).ok();
|
||||||
|
|
||||||
let mut command = match snap.config.rustfmt() {
|
let mut command = match snap.config.rustfmt(source_root_id) {
|
||||||
RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
|
RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
|
||||||
// FIXME: Set RUSTUP_TOOLCHAIN
|
// FIXME: Set RUSTUP_TOOLCHAIN
|
||||||
let mut cmd = process::Command::new(toolchain::Tool::Rustfmt.path());
|
let mut cmd = process::Command::new(toolchain::Tool::Rustfmt.path());
|
||||||
|
@ -2302,9 +2303,8 @@ pub(crate) fn internal_testing_fetch_config(
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
serde_json::to_value(match &*params.config {
|
serde_json::to_value(match &*params.config {
|
||||||
"local" => state.config.assist(source_root).assist_emit_must_use,
|
"local" => state.config.assist(source_root).assist_emit_must_use,
|
||||||
// TODO Most probably will fail because it was not renamed to workspace
|
"workspace" => matches!(
|
||||||
"global" => matches!(
|
state.config.rustfmt(source_root),
|
||||||
state.config.rustfmt(),
|
|
||||||
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. }
|
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. }
|
||||||
),
|
),
|
||||||
_ => return Err(anyhow::anyhow!("Unknown test config key: {}", params.config)),
|
_ => return Err(anyhow::anyhow!("Unknown test config key: {}", params.config)),
|
||||||
|
|
|
@ -67,7 +67,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
|
||||||
code_action_provider: Some(config.caps().code_action_capabilities()),
|
code_action_provider: Some(config.caps().code_action_capabilities()),
|
||||||
code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
|
code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
|
||||||
document_formatting_provider: Some(OneOf::Left(true)),
|
document_formatting_provider: Some(OneOf::Left(true)),
|
||||||
document_range_formatting_provider: match config.rustfmt() {
|
document_range_formatting_provider: match config.rustfmt(None) {
|
||||||
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)),
|
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)),
|
||||||
_ => Some(OneOf::Left(false)),
|
_ => Some(OneOf::Left(false)),
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,7 +17,7 @@ enum QueryType {
|
||||||
/// A query whose config key is a part of the global configs, so that
|
/// A query whose config key is a part of the global configs, so that
|
||||||
/// testing for changes to this config means testing if global changes
|
/// testing for changes to this config means testing if global changes
|
||||||
/// take affect.
|
/// take affect.
|
||||||
Global,
|
Workspace,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RatomlTest {
|
struct RatomlTest {
|
||||||
|
@ -165,7 +165,7 @@ impl RatomlTest {
|
||||||
fn query(&self, query: QueryType, source_file_idx: usize) -> bool {
|
fn query(&self, query: QueryType, source_file_idx: usize) -> bool {
|
||||||
let config = match query {
|
let config = match query {
|
||||||
QueryType::Local => "local".to_owned(),
|
QueryType::Local => "local".to_owned(),
|
||||||
QueryType::Global => "global".to_owned(),
|
QueryType::Workspace => "workspace".to_owned(),
|
||||||
};
|
};
|
||||||
let res = self.server.send_request::<InternalTestingFetchConfig>(
|
let res = self.server.send_request::<InternalTestingFetchConfig>(
|
||||||
InternalTestingFetchConfigParams {
|
InternalTestingFetchConfigParams {
|
||||||
|
@ -823,10 +823,8 @@ fn ratoml_multiple_ratoml_in_single_source_root() {
|
||||||
// assert!(!server.query(QueryType::AssistEmitMustUse, 5));
|
// assert!(!server.query(QueryType::AssistEmitMustUse, 5));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/// Having a ratoml file at the root of a project enables
|
|
||||||
/// configuring global level configurations as well.
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ratoml_in_root_is_global() {
|
fn ratoml_in_root_is_workspace() {
|
||||||
if skip_slow_tests() {
|
if skip_slow_tests() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -854,7 +852,7 @@ fn main() {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(server.query(QueryType::Global, 2));
|
assert!(server.query(QueryType::Workspace, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -886,9 +884,9 @@ fn main() {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(server.query(QueryType::Global, 2));
|
assert!(server.query(QueryType::Workspace, 2));
|
||||||
server.edit(1, "rustfmt.rangeFormatting.enable = false".to_owned());
|
server.edit(1, "rustfmt.rangeFormatting.enable = false".to_owned());
|
||||||
assert!(!server.query(QueryType::Global, 2));
|
assert!(!server.query(QueryType::Workspace, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -920,7 +918,7 @@ fn main() {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(server.query(QueryType::Global, 2));
|
assert!(server.query(QueryType::Workspace, 2));
|
||||||
server.delete(1);
|
server.delete(1);
|
||||||
assert!(!server.query(QueryType::Global, 2));
|
assert!(!server.query(QueryType::Workspace, 2));
|
||||||
}
|
}
|
||||||
|
|
|
@ -878,27 +878,6 @@ crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
|
||||||
|
|
||||||
This option does not take effect until rust-analyzer is restarted.
|
This option does not take effect until rust-analyzer is restarted.
|
||||||
--
|
--
|
||||||
[[rust-analyzer.rustfmt.extraArgs]]rust-analyzer.rustfmt.extraArgs (default: `[]`)::
|
|
||||||
+
|
|
||||||
--
|
|
||||||
Additional arguments to `rustfmt`.
|
|
||||||
--
|
|
||||||
[[rust-analyzer.rustfmt.overrideCommand]]rust-analyzer.rustfmt.overrideCommand (default: `null`)::
|
|
||||||
+
|
|
||||||
--
|
|
||||||
Advanced option, fully override the command rust-analyzer uses for
|
|
||||||
formatting. This should be the equivalent of `rustfmt` here, and
|
|
||||||
not that of `cargo fmt`. The file contents will be passed on the
|
|
||||||
standard input and the formatted result will be read from the
|
|
||||||
standard output.
|
|
||||||
--
|
|
||||||
[[rust-analyzer.rustfmt.rangeFormatting.enable]]rust-analyzer.rustfmt.rangeFormatting.enable (default: `false`)::
|
|
||||||
+
|
|
||||||
--
|
|
||||||
Enables the use of rustfmt's unstable range formatting command for the
|
|
||||||
`textDocument/rangeFormatting` request. The rustfmt option is unstable and only
|
|
||||||
available on a nightly build.
|
|
||||||
--
|
|
||||||
[[rust-analyzer.semanticHighlighting.doc.comment.inject.enable]]rust-analyzer.semanticHighlighting.doc.comment.inject.enable (default: `true`)::
|
[[rust-analyzer.semanticHighlighting.doc.comment.inject.enable]]rust-analyzer.semanticHighlighting.doc.comment.inject.enable (default: `true`)::
|
||||||
+
|
+
|
||||||
--
|
--
|
||||||
|
|
|
@ -2358,45 +2358,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"title": "rustfmt",
|
|
||||||
"properties": {
|
|
||||||
"rust-analyzer.rustfmt.extraArgs": {
|
|
||||||
"markdownDescription": "Additional arguments to `rustfmt`.",
|
|
||||||
"default": [],
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "rustfmt",
|
|
||||||
"properties": {
|
|
||||||
"rust-analyzer.rustfmt.overrideCommand": {
|
|
||||||
"markdownDescription": "Advanced option, fully override the command rust-analyzer uses for\nformatting. This should be the equivalent of `rustfmt` here, and\nnot that of `cargo fmt`. The file contents will be passed on the\nstandard input and the formatted result will be read from the\nstandard output.",
|
|
||||||
"default": null,
|
|
||||||
"type": [
|
|
||||||
"null",
|
|
||||||
"array"
|
|
||||||
],
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "rustfmt",
|
|
||||||
"properties": {
|
|
||||||
"rust-analyzer.rustfmt.rangeFormatting.enable": {
|
|
||||||
"markdownDescription": "Enables the use of rustfmt's unstable range formatting command for the\n`textDocument/rangeFormatting` request. The rustfmt option is unstable and only\navailable on a nightly build.",
|
|
||||||
"default": false,
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"title": "semanticHighlighting",
|
"title": "semanticHighlighting",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
Loading…
Reference in a new issue