1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
use serde::{Serialize, Deserialize};

/// Information about a plugin.
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PluginInfo {
    /// The name of the plugin.
    pub name: String,
    /// The version of the plugin.
    pub version: String,
    /// Gets the possible keys that can be used in the configuration JSON.
    pub config_keys: Vec<String>,
    /// The file extensions this plugin supports.
    pub file_extensions: Vec<String>,
    /// A url the user can go to in order to get help information about the plugin.
    pub help_url: String,
}

/// The plugin system schema version that is incremented
/// when there are any breaking changes.
pub const PLUGIN_SYSTEM_SCHEMA_VERSION: u32 = 1;

#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub mod macros {
    #[macro_export]
    macro_rules! generate_plugin_code {
        () => {
            // FORMATTING

            static mut FILE_PATH: Option<PathBuf> = None;
            static mut FORMATTED_TEXT: Option<String> = None;
            static mut ERROR_TEXT: Option<String> = None;

            #[no_mangle]
            pub fn set_file_path() {
                let text = take_string_from_shared_bytes();
                unsafe { FILE_PATH.replace(PathBuf::from(text)) };
            }

            #[no_mangle]
            pub fn format() -> u8 {
                ensure_initialized();
                let file_path = unsafe { FILE_PATH.take().expect("Expected the file path to be set.") };
                let file_text = take_string_from_shared_bytes();

                let formatted_text = format_text(&file_path, &file_text, &get_resolved_config_result().config);
                match formatted_text {
                    Ok(formatted_text) => {
                        if formatted_text == file_text {
                            0 // no change
                        } else {
                            unsafe { FORMATTED_TEXT.replace(formatted_text) };
                            1 // change
                        }
                    },
                    Err(err_text) => {
                        unsafe { ERROR_TEXT.replace(err_text) };
                        2 // error
                    }
                }
            }

            #[no_mangle]
            pub fn get_formatted_text() -> usize {
                let formatted_text = unsafe { FORMATTED_TEXT.take().expect("Expected to have formatted text.") };
                set_shared_bytes_str(formatted_text)
            }

            #[no_mangle]
            pub fn get_error_text() -> usize {
                let error_text = unsafe { ERROR_TEXT.take().expect("Expected to have error text.") };
                set_shared_bytes_str(error_text)
            }

            // CONFIGURATION

            static mut RESOLVE_CONFIGURATION_RESULT: Option<dprint_core::configuration::ResolveConfigurationResult<Configuration>> = None;

            #[no_mangle]
            pub fn get_plugin_info() -> usize {
                use dprint_core::plugins::PluginInfo;
                let info_json = serde_json::to_string(&PluginInfo {
                    name: String::from(env!("CARGO_PKG_NAME")),
                    version: String::from(env!("CARGO_PKG_VERSION")),
                    config_keys: get_plugin_config_keys(),
                    file_extensions: get_plugin_file_extensions(),
                    help_url: get_plugin_help_url(),
                }).unwrap();
                set_shared_bytes_str(info_json)
            }

            #[no_mangle]
            pub fn get_resolved_config() -> usize {
                let json = serde_json::to_string(&get_resolved_config_result().config).unwrap();
                set_shared_bytes_str(json)
            }

            #[no_mangle]
            pub fn get_config_diagnostics() -> usize {
                let json = serde_json::to_string(&get_resolved_config_result().diagnostics).unwrap();
                set_shared_bytes_str(json)
            }

            fn get_resolved_config_result<'a>() -> &'a dprint_core::configuration::ResolveConfigurationResult<Configuration> {
                unsafe {
                    ensure_initialized();
                    return RESOLVE_CONFIGURATION_RESULT.as_ref().unwrap();
                }
            }

            fn ensure_initialized() {
                unsafe {
                    if RESOLVE_CONFIGURATION_RESULT.is_none() {
                        if let Some(global_config) = GLOBAL_CONFIG.take() {
                            if let Some(plugin_config) = PLUGIN_CONFIG.take() {
                                let config_result = resolve_config(plugin_config, &global_config);
                                RESOLVE_CONFIGURATION_RESULT.replace(config_result);
                                return;
                            }
                        }

                        panic!("Plugin must have global config and plugin config set before use.");
                    }
                }
            }

            // INITIALIZATION

            static mut GLOBAL_CONFIG: Option<dprint_core::configuration::GlobalConfiguration> = None;
            static mut PLUGIN_CONFIG: Option<std::collections::HashMap<String, String>> = None;

            #[no_mangle]
            pub fn set_global_config() {
                let text = take_string_from_shared_bytes();
                let global_config: dprint_core::configuration::GlobalConfiguration = serde_json::from_str(&text).unwrap();
                unsafe { GLOBAL_CONFIG.replace(global_config); }
            }

            #[no_mangle]
            pub fn set_plugin_config() {
                let text = take_string_from_shared_bytes();
                let plugin_config: std::collections::HashMap<String, String> = serde_json::from_str(&text).unwrap();
                unsafe { PLUGIN_CONFIG.replace(plugin_config); }
            }

            // LOW LEVEL SENDING AND RECEIVING

            const WASM_MEMORY_BUFFER_SIZE: usize = 1024;
            static mut WASM_MEMORY_BUFFER: [u8; WASM_MEMORY_BUFFER_SIZE] = [0; WASM_MEMORY_BUFFER_SIZE];
            static mut SHARED_BYTES: Vec<u8> = Vec::new();

            #[no_mangle]
            pub fn get_plugin_schema_version() -> u32 {
                dprint_core::plugins::PLUGIN_SYSTEM_SCHEMA_VERSION // version 1
            }

            #[no_mangle]
            pub fn get_wasm_memory_buffer() -> *const u8 {
                unsafe { WASM_MEMORY_BUFFER.as_ptr() }
            }

            #[no_mangle]
            pub fn get_wasm_memory_buffer_size() -> usize {
                WASM_MEMORY_BUFFER_SIZE
            }

            #[no_mangle]
            pub fn add_to_shared_bytes_from_buffer(length: usize) {
                unsafe {
                    SHARED_BYTES.extend(&WASM_MEMORY_BUFFER[..length])
                }
            }

            #[no_mangle]
            pub fn set_buffer_with_shared_bytes(offset: usize, length: usize) {
                unsafe {
                    let bytes = &SHARED_BYTES[offset..(offset+length)];
                    &WASM_MEMORY_BUFFER[..length].copy_from_slice(bytes);
                }
            }

            #[no_mangle]
            pub fn clear_shared_bytes(capacity: usize) {
                unsafe { SHARED_BYTES = Vec::with_capacity(capacity); }
            }

            fn take_string_from_shared_bytes() -> String {
                unsafe {
                    let bytes = std::mem::replace(&mut SHARED_BYTES, Vec::with_capacity(0));
                    String::from_utf8(bytes).unwrap()
                }
            }

            fn set_shared_bytes_str(text: String) -> usize {
                let length = text.len();
                unsafe { SHARED_BYTES = text.into_bytes() }
                length
            }
        }
   }
}