removing the language and theme configurations in favor of a better implementation in the future

pull/8675/merge^2
mattwparas 10 months ago
parent c902a23256
commit 38f344c21c

@ -9,8 +9,12 @@ pub mod steel_implementations {
steel_vm::{builtin::BuiltInModule, register_fn::RegisterFn}, steel_vm::{builtin::BuiltInModule, register_fn::RegisterFn},
}; };
use crate::syntax::{AutoPairConfig, SoftWrap};
impl steel::rvals::Custom for crate::Position {} impl steel::rvals::Custom for crate::Position {}
impl steel::rvals::Custom for crate::Selection {} impl steel::rvals::Custom for crate::Selection {}
impl steel::rvals::Custom for AutoPairConfig {}
impl steel::rvals::Custom for SoftWrap {}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
enum SliceKind { enum SliceKind {

@ -386,18 +386,6 @@ impl Application {
pub fn handle_config_events(&mut self, config_event: ConfigEvent) { pub fn handle_config_events(&mut self, config_event: ConfigEvent) {
match config_event { match config_event {
ConfigEvent::Refresh => self.refresh_config(), ConfigEvent::Refresh => self.refresh_config(),
ConfigEvent::UpdateLanguageConfiguration => match self.refresh_language_config() {
Ok(_) => {
// If we don't stash the theme here, the syntax highlighting is broken
let current_theme = std::mem::take(&mut self.editor.theme);
self.editor.set_theme(current_theme);
self.editor.set_status("Language config refreshed");
}
Err(err) => {
self.editor.set_error(err.to_string());
}
},
// Since only the Application can make changes to Editor's config, // Since only the Application can make changes to Editor's config,
// the Editor must send up a new copy of a modified config so that // the Editor must send up a new copy of a modified config so that
@ -427,8 +415,7 @@ impl Application {
/// refresh language config after config change /// refresh language config after config change
fn refresh_language_config(&mut self) -> Result<(), Error> { fn refresh_language_config(&mut self) -> Result<(), Error> {
let syntax_config = crate::commands::engine::ScriptingEngine::load_language_configuration() let syntax_config = helix_core::config::user_syntax_loader()
.unwrap_or_else(|| helix_core::config::user_syntax_loader())
.map_err(|err| anyhow::anyhow!("Failed to load language config: {}", err))?; .map_err(|err| anyhow::anyhow!("Failed to load language config: {}", err))?;
self.syn_loader = std::sync::Arc::new(syntax::Loader::new(syntax_config)); self.syn_loader = std::sync::Arc::new(syntax::Loader::new(syntax_config));
@ -452,19 +439,15 @@ impl Application {
let theme = config let theme = config
.theme .theme
.as_ref() .as_ref()
.and_then(|theme| crate::commands::engine::ScriptingEngine::load_theme(theme)) .and_then(|theme| {
.or_else(|| { self.theme_loader
// Check the name again .load(theme)
config.theme.as_ref().and_then(|theme| { .map_err(|e| {
self.theme_loader log::warn!("failed to load theme `{}` - {}", theme, e);
.load(theme) e
.map_err(|e| { })
log::warn!("failed to load theme `{}` - {}", theme, e); .ok()
e .filter(|theme| (true_color || theme.is_16_color()))
})
.ok()
.filter(|theme| (true_color || theme.is_16_color()))
})
}) })
.unwrap_or_else(|| self.theme_loader.default_theme(true_color)); .unwrap_or_else(|| self.theme_loader.default_theme(true_color));

@ -138,42 +138,6 @@ impl ScriptingEngine {
.flat_map(|kind| manual_dispatch!(kind, available_commands())) .flat_map(|kind| manual_dispatch!(kind, available_commands()))
.collect() .collect()
} }
pub fn load_theme(name: &str) -> Option<Theme> {
for kind in PLUGIN_PRECEDENCE {
let theme = manual_dispatch!(kind, load_theme(name));
if theme.is_some() {
return theme;
}
}
None
}
pub fn themes() -> Option<Vec<String>> {
for kind in PLUGIN_PRECEDENCE {
let themes = manual_dispatch!(kind, themes());
if themes.is_some() {
return themes;
}
}
None
}
pub fn load_language_configuration() -> Option<Result<Configuration, toml::de::Error>> {
for kind in PLUGIN_PRECEDENCE {
let config = manual_dispatch!(kind, load_language_configuration());
if config.is_some() {
return config;
}
}
None
}
} }
impl PluginSystem for NoEngine { impl PluginSystem for NoEngine {
@ -251,25 +215,4 @@ pub trait PluginSystem {
fn available_commands<'a>(&self) -> Vec<Cow<'a, str>> { fn available_commands<'a>(&self) -> Vec<Cow<'a, str>> {
Vec::new() Vec::new()
} }
/// Retrieve a theme for a given name
#[inline(always)]
fn load_theme(&self, _name: &str) -> Option<Theme> {
None
}
/// Retrieve the list of themes that exist within the runtime
#[inline(always)]
fn themes(&self) -> Option<Vec<String>> {
None
}
/// Fetch the language configuration as monitored by the plugin system.
///
/// For now - this maintains backwards compatibility with the existing toml configuration,
/// and as such the toml error is exposed here.
#[inline(always)]
fn load_language_configuration(&self) -> Option<Result<Configuration, toml::de::Error>> {
None
}
} }

@ -4,12 +4,10 @@ use helix_core::{
graphemes, graphemes,
regex::Regex, regex::Regex,
shellwords::Shellwords, shellwords::Shellwords,
syntax::{AutoPairConfig, Configuration, SoftWrap}, syntax::{AutoPairConfig, SoftWrap},
Range, Selection, Tendril, Range, Selection, Tendril,
}; };
use helix_event::register_hook; use helix_event::register_hook;
use helix_loader::{config_dir, merge_toml_values};
use helix_lsp::lsp::CompletionItem;
use helix_stdx::path::expand_tilde; use helix_stdx::path::expand_tilde;
use helix_view::{ use helix_view::{
document::Mode, document::Mode,
@ -20,7 +18,7 @@ use helix_view::{
}, },
extension::document_id_to_usize, extension::document_id_to_usize,
input::KeyEvent, input::KeyEvent,
Document, DocumentId, Editor, Theme, ViewId, Document, DocumentId, Editor, ViewId,
}; };
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde_json::Value; use serde_json::Value;
@ -33,8 +31,8 @@ use steel::{
}; };
use std::{ use std::{
borrow::Cow, cell::RefCell, collections::HashMap, ops::Deref, path::PathBuf, rc::Rc, borrow::Cow, collections::HashMap, ops::Deref, path::PathBuf, sync::atomic::AtomicUsize,
sync::atomic::AtomicUsize, time::Duration, time::Duration,
}; };
use std::{ use std::{
collections::HashSet, collections::HashSet,
@ -58,18 +56,15 @@ use components::SteelDynamicComponent;
use super::{components, Context, MappableCommand, TYPABLE_COMMAND_LIST}; use super::{components, Context, MappableCommand, TYPABLE_COMMAND_LIST};
use insert::{insert_char, insert_string}; use insert::{insert_char, insert_string};
// TODO:
// I'm not entirely sure this is correct, however from observation it seems that
// interactions with this really only happen on one thread. I haven't yet found
// multiple instances of the engine getting created. I'll need to go observe how
// and when the engine gets accessed.
thread_local! { thread_local! {
pub static ENGINE: std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine::Engine>> = configure_engine(); pub static ENGINE: std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine::Engine>> = configure_engine();
} }
// APIs / Modules that need to be accepted by the plugin system
// Without these, the core functionality cannot operate
// pub struct DocumentApi;
// pub struct EditorApi;
// pub struct ComponentApi;
// pub struct TypedCommandsApi;
// pub struct StaticCommandsApi;
pub struct KeyMapApi { pub struct KeyMapApi {
get_keymap: fn() -> EmbeddedKeyMap, get_keymap: fn() -> EmbeddedKeyMap,
default_keymap: fn() -> EmbeddedKeyMap, default_keymap: fn() -> EmbeddedKeyMap,
@ -94,6 +89,12 @@ impl KeyMapApi {
} }
} }
// TODO: Refactor this into the configuration object instead.
// that way, we don't have to have multiple layers of objects,
// and can instead just refer to the single configuration object.
//
// Possibly also introduce buffer / language specific keybindings
// there as well.
thread_local! { thread_local! {
pub static BUFFER_OR_EXTENSION_KEYBINDING_MAP: SteelVal = pub static BUFFER_OR_EXTENSION_KEYBINDING_MAP: SteelVal =
SteelVal::boxed(SteelVal::empty_hashmap()); SteelVal::boxed(SteelVal::empty_hashmap());
@ -102,129 +103,6 @@ thread_local! {
SteelVal::boxed(SteelVal::empty_hashmap()); SteelVal::boxed(SteelVal::empty_hashmap());
pub static GLOBAL_KEYBINDING_MAP: SteelVal = get_keymap().into_steelval().unwrap(); pub static GLOBAL_KEYBINDING_MAP: SteelVal = get_keymap().into_steelval().unwrap();
static THEME_MAP: ThemeContainer = ThemeContainer::new();
static LANGUAGE_CONFIGURATIONS: LanguageConfigurationContainer = LanguageConfigurationContainer::new();
}
// Any configurations that we'd like to overlay from the toml
struct LanguageConfigurationContainer {
configuration: Rc<RefCell<Option<toml::Value>>>,
}
impl LanguageConfigurationContainer {
fn new() -> Self {
Self {
configuration: Rc::new(RefCell::new(None)),
}
}
}
impl Custom for LanguageConfigurationContainer {}
impl LanguageConfigurationContainer {
fn add_configuration(&self, config_as_string: String) -> Result<(), String> {
let left = self
.configuration
.replace(Some(toml::Value::Boolean(false)));
if let Some(left) = left {
let right = serde_json::from_str(&config_as_string).map_err(|err| err.to_string());
match right {
Ok(right) => {
self.configuration
.replace(Some(merge_toml_values(left, right, 3)));
Ok(())
}
Err(e) => Err(e),
}
} else {
let right = serde_json::from_str(&config_as_string).map_err(|err| err.to_string())?;
self.configuration.replace(Some(right));
Ok(())
}
}
fn as_language_configuration(&self) -> Option<Result<Configuration, toml::de::Error>> {
if let Some(right) = self.configuration.borrow().clone() {
let config = helix_loader::config::user_lang_config();
let res = config
.map(|left| merge_toml_values(left, right, 3))
.and_then(|x| x.try_into());
Some(res)
// )
} else {
None
}
}
fn get_language_configuration() -> Option<Result<Configuration, toml::de::Error>> {
LANGUAGE_CONFIGURATIONS.with(|x| x.as_language_configuration())
}
}
struct ThemeContainer {
themes: Rc<RefCell<HashMap<String, Theme>>>,
}
impl Custom for ThemeContainer {}
impl ThemeContainer {
fn new() -> Self {
Self {
themes: Rc::new(RefCell::new(HashMap::new())),
}
}
fn get(name: &str) -> Option<Theme> {
THEME_MAP.with(|x| x.themes.borrow().get(name).cloned())
}
fn names() -> Vec<String> {
THEME_MAP.with(|x| x.themes.borrow().keys().cloned().collect())
}
fn register(name: String, theme: Theme) {
THEME_MAP.with(|x| x.themes.borrow_mut().insert(name, theme));
}
}
fn load_language_configuration_api(engine: &mut Engine) {
let mut module = BuiltInModule::new("helix/core/languages".to_string());
module.register_fn(
"register-language-configuration!",
|language_configuration: String| -> Result<(), String> {
LANGUAGE_CONFIGURATIONS.with(|x| x.add_configuration(language_configuration))
},
);
module.register_fn("flush-configuration", refresh_language_configuration);
engine.register_module(module);
}
fn load_theme_api(engine: &mut Engine) {
let mut module = BuiltInModule::new("helix/core/themes");
module.register_fn(
"register-theme!",
|name: String, theme_as_json_string: String| -> Result<(), String> {
Ok(ThemeContainer::register(
name,
serde_json::from_str(&theme_as_json_string).map_err(|err| err.to_string())?,
))
},
);
engine.register_module(module);
} }
fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) { fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) {
@ -239,11 +117,6 @@ fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) {
module.register_fn("helix-deep-copy-keymap", api.deep_copy_keymap); module.register_fn("helix-deep-copy-keymap", api.deep_copy_keymap);
// Alternatively, could store these values in a steel module, like so:
// let keymap_core_map = helix_loader::runtime_file(&PathBuf::from("steel").join("keymap.scm"));
// let require_module = format!("(require {})", keymap_core_map.to_str().unwrap());
// engine.run(&require_module).unwrap();
// This should be associated with a corresponding scheme module to wrap this up // This should be associated with a corresponding scheme module to wrap this up
module.register_value( module.register_value(
"*buffer-or-extension-keybindings*", "*buffer-or-extension-keybindings*",
@ -332,8 +205,6 @@ fn load_static_commands(engine: &mut Engine) {
template_function_arity_1("regex-selection"); template_function_arity_1("regex-selection");
module.register_fn("replace-selection-with", replace_selection); module.register_fn("replace-selection-with", replace_selection);
template_function_arity_1("replace-selection-with"); template_function_arity_1("replace-selection-with");
// module.register_fn("show-completion-prompt-with", show_completion_prompt);
// template_function_arity_1("show-completion-prompt-with");
module.register_fn("cx->current-file", current_path); module.register_fn("cx->current-file", current_path);
template_function_arity_1("cx->current-file"); template_function_arity_1("cx->current-file");
@ -370,9 +241,6 @@ fn load_static_commands(engine: &mut Engine) {
module.register_fn("move-window-far-right", move_window_to_the_right); module.register_fn("move-window-far-right", move_window_to_the_right);
template_function_arity_0("move-window-far-right"); template_function_arity_0("move-window-far-right");
// This should probably just get removed?
// module.register_fn("block-on-shell-command", run_shell_command_text);
let mut template_function_no_context = |name: &str| { let mut template_function_no_context = |name: &str| {
builtin_static_command_module.push_str(&format!( builtin_static_command_module.push_str(&format!(
r#" r#"
@ -389,9 +257,6 @@ fn load_static_commands(engine: &mut Engine) {
template_function_no_context("get-helix-scm-path"); template_function_no_context("get-helix-scm-path");
template_function_no_context("get-init-scm-path"); template_function_no_context("get-init-scm-path");
// let mut target_directory = PathBuf::from(std::env::var("HELIX_RUNTIME").unwrap());
// target_directory.push("cogs");
// target_directory.push("helix");
let mut target_directory = helix_runtime_search_path(); let mut target_directory = helix_runtime_search_path();
if !target_directory.exists() { if !target_directory.exists() {
@ -476,10 +341,6 @@ fn load_typed_commands(engine: &mut Engine) {
)); ));
} }
// let mut target_directory = PathBuf::from(std::env::var("HELIX_RUNTIME").unwrap());
// target_directory.push("cogs");
// target_directory.push("helix");
let mut target_directory = helix_runtime_search_path(); let mut target_directory = helix_runtime_search_path();
if !target_directory.exists() { if !target_directory.exists() {
std::fs::create_dir(&target_directory).unwrap(); std::fs::create_dir(&target_directory).unwrap();
@ -622,7 +483,7 @@ fn load_configuration_api(engine: &mut Engine) {
"cursorline", "cursorline",
"cursorcolumn", "cursorcolumn",
"middle-click-paste", "middle-click-paste",
// "auto-pairs", "auto-pairs",
"auto-completion", "auto-completion",
"auto-format", "auto-format",
"auto-save", "auto-save",
@ -667,7 +528,14 @@ fn load_configuration_api(engine: &mut Engine) {
.register_fn("cursorline", HelixConfiguration::cursorline) .register_fn("cursorline", HelixConfiguration::cursorline)
.register_fn("cursorcolumn", HelixConfiguration::cursorcolumn) .register_fn("cursorcolumn", HelixConfiguration::cursorcolumn)
.register_fn("middle-click-paste", HelixConfiguration::middle_click_paste) .register_fn("middle-click-paste", HelixConfiguration::middle_click_paste)
// .register_fn("auto-pairs", HelixConfiguration::auto_pairs) .register_fn("auto-pairs", HelixConfiguration::auto_pairs)
// Specific constructors for the auto pairs configuration
.register_fn("auto-pairs-default", |enabled: bool| {
AutoPairConfig::Enable(enabled)
})
.register_fn("auto-pairs-map", |map: HashMap<char, char>| {
AutoPairConfig::Pairs(map)
})
.register_fn("auto-completion", HelixConfiguration::auto_completion) .register_fn("auto-completion", HelixConfiguration::auto_completion)
.register_fn("auto-format", HelixConfiguration::auto_format) .register_fn("auto-format", HelixConfiguration::auto_format)
.register_fn("auto-save", HelixConfiguration::auto_save) .register_fn("auto-save", HelixConfiguration::auto_save)
@ -713,9 +581,6 @@ fn load_configuration_api(engine: &mut Engine) {
) )
.register_fn("smart-tab", HelixConfiguration::smart_tab); .register_fn("smart-tab", HelixConfiguration::smart_tab);
// let mut target_directory = PathBuf::from(std::env::var("HELIX_RUNTIME").unwrap());
// target_directory.push("cogs");
// target_directory.push("helix");
let mut target_directory = helix_runtime_search_path(); let mut target_directory = helix_runtime_search_path();
if !target_directory.exists() { if !target_directory.exists() {
@ -988,24 +853,6 @@ impl super::PluginSystem for SteelScriptingEngine {
.map(|x| x.clone().into()) .map(|x| x.clone().into())
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
fn load_theme(&self, name: &str) -> Option<helix_view::Theme> {
ThemeContainer::get(name)
}
fn themes(&self) -> Option<Vec<String>> {
let names = ThemeContainer::names();
if !names.is_empty() {
Some(names)
} else {
None
}
}
fn load_language_configuration(&self) -> Option<Result<Configuration, toml::de::Error>> {
LanguageConfigurationContainer::get_language_configuration()
}
} }
impl SteelScriptingEngine { impl SteelScriptingEngine {
@ -1963,13 +1810,9 @@ fn configure_engine_impl(mut engine: Engine) -> Engine {
load_typed_commands(&mut engine); load_typed_commands(&mut engine);
load_static_commands(&mut engine); load_static_commands(&mut engine);
load_keymap_api(&mut engine, KeyMapApi::new()); load_keymap_api(&mut engine, KeyMapApi::new());
load_theme_api(&mut engine);
load_rope_api(&mut engine); load_rope_api(&mut engine);
load_language_configuration_api(&mut engine);
load_misc_api(&mut engine); load_misc_api(&mut engine);
// load_engine_api(&mut engine);
// Hooks // Hooks
engine.register_fn("register-hook!", register_hook); engine.register_fn("register-hook!", register_hook);
engine.register_fn("log::info!", |message: String| log::info!("{}", message)); engine.register_fn("log::info!", |message: String| log::info!("{}", message));
@ -2177,9 +2020,6 @@ fn configure_engine_impl(mut engine: Engine) -> Engine {
fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine::Engine>> { fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine::Engine>> {
let engine = configure_engine_impl(steel::steel_vm::engine::Engine::new()); let engine = configure_engine_impl(steel::steel_vm::engine::Engine::new());
// engine.run(&"(require \"/home/matt/Documents/helix-fork/helix/helix-term/src/commands/engine/controller.scm\"
// (for-syntax \"/home/matt/Documents/helix-fork/helix/helix-term/src/commands/engine/controller.scm\"))").unwrap();
std::rc::Rc::new(std::cell::RefCell::new(engine)) std::rc::Rc::new(std::cell::RefCell::new(engine))
} }
@ -2573,15 +2413,6 @@ fn set_options(
Ok(()) Ok(())
} }
pub fn refresh_language_configuration(cx: &mut Context) -> anyhow::Result<()> {
cx.editor
.config_events
.0
.send(ConfigEvent::UpdateLanguageConfiguration)?;
Ok(())
}
pub fn cx_pos_within_text(cx: &mut Context) -> usize { pub fn cx_pos_within_text(cx: &mut Context) -> usize {
let (view, doc) = current_ref!(cx.editor); let (view, doc) = current_ref!(cx.editor);
@ -2735,43 +2566,6 @@ fn replace_selection(cx: &mut Context, value: String) {
doc.apply(&transaction, view.id); doc.apply(&transaction, view.id);
} }
// fn show_completion_prompt(cx: &mut Context, items: Vec<String>) {
// let (view, doc) = current!(cx.editor);
// let items = items
// .into_iter()
// .map(|x| crate::ui::CompletionItem {
// item: CompletionItem::new_simple(x, "".to_string()),
// language_server_id: usize::MAX,
// resolved: true,
// })
// .collect();
// let text = doc.text();
// let cursor = doc.selection(view.id).primary().cursor(text.slice(..));
// let trigger = crate::handlers::completion::Trigger::new(
// cursor,
// view.id,
// doc.id(),
// crate::handlers::completion::TriggerKind::Manual,
// );
// let savepoint = doc.savepoint(view);
// let callback = async move {
// let call: job::Callback = Callback::EditorCompositor(Box::new(
// move |editor: &mut Editor, compositor: &mut Compositor| {
// crate::handlers::completion::show_completion(
// editor, compositor, items, trigger, savepoint,
// );
// },
// ));
// Ok(call)
// };
// cx.jobs.callback(callback);
// }
// TODO: Remove this! // TODO: Remove this!
fn move_window_to_the_left(cx: &mut Context) { fn move_window_to_the_left(cx: &mut Context) {
while cx while cx

@ -880,10 +880,7 @@ fn theme(
// Ensures that a preview theme gets cleaned up if the user backspaces until the prompt is empty. // Ensures that a preview theme gets cleaned up if the user backspaces until the prompt is empty.
cx.editor.unset_theme_preview(); cx.editor.unset_theme_preview();
} else if let Some(theme_name) = args.first() { } else if let Some(theme_name) = args.first() {
if let Ok(theme) = crate::commands::engine::ScriptingEngine::load_theme(theme_name) if let Ok(theme) = cx.editor.theme_loader.load(theme_name) {
.map(|x| Ok(x))
.unwrap_or_else(|| cx.editor.theme_loader.load(theme_name))
{
if !(true_color || theme.is_16_color()) { if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support"); bail!("Unsupported theme: theme requires true color support");
} }
@ -893,25 +890,15 @@ fn theme(
} }
PromptEvent::Validate => { PromptEvent::Validate => {
if let Some(theme_name) = args.first() { if let Some(theme_name) = args.first() {
// let theme = cx let theme = cx
// .editor .editor
// .theme_loader .theme_loader
// .load(theme_name) .load(theme_name)
// .map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?; .map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?;
// if !(true_color || theme.is_16_color()) { if !(true_color || theme.is_16_color()) {
// bail!("Unsupported theme: theme requires true color support"); bail!("Unsupported theme: theme requires true color support");
// } }
// cx.editor.set_theme(theme); cx.editor.set_theme(theme);
if let Ok(theme) = crate::commands::engine::ScriptingEngine::load_theme(theme_name)
.map(|x| Ok(x))
.unwrap_or_else(|| cx.editor.theme_loader.load(theme_name))
{
if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support");
}
cx.editor.set_theme(theme);
};
} else { } else {
let name = cx.editor.theme.name().to_string(); let name = cx.editor.theme.name().to_string();

@ -274,10 +274,6 @@ pub mod completers {
names.extend(theme::Loader::read_names(&rt_dir.join("themes"))); names.extend(theme::Loader::read_names(&rt_dir.join("themes")));
} }
if let Some(themes) = crate::commands::engine::ScriptingEngine::themes() {
names.extend(themes);
}
names.push("default".into()); names.push("default".into());
names.push("base16_default".into()); names.push("base16_default".into());
names.sort(); names.sort();

@ -979,7 +979,6 @@ pub enum EditorEvent {
pub enum ConfigEvent { pub enum ConfigEvent {
Refresh, Refresh,
Update(Box<Config>), Update(Box<Config>),
UpdateLanguageConfiguration,
Change, Change,
} }

Loading…
Cancel
Save