json_language_server_ext.rs

  1use ::serde::{Deserialize, Serialize};
  2use gpui::{App, Entity, WeakEntity};
  3use language::Buffer;
  4use language::{File as _, LocalFile as _};
  5use lsp::{DidCloseTextDocumentParams, DidOpenTextDocumentParams, LanguageServer};
  6use util::ResultExt as _;
  7
  8use crate::{LspStore, Project};
  9
 10// https://github.com/microsoft/vscode/blob/main/extensions/json-language-features/server/README.md#schema-associations-notification
 11struct SchemaAssociationsNotification {}
 12
 13/// interface ISchemaAssociation {
 14///   /**
 15///    * The URI of the schema, which is also the identifier of the schema.
 16///    */
 17///   uri: string;
 18///
 19///   /**
 20///    * A list of file path patterns that are associated to the schema. The '*' wildcard can be used. Exclusion patterns starting with '!'.
 21///    * For example '*.schema.json', 'package.json', '!foo*.schema.json'.
 22///    * A match succeeds when there is at least one pattern matching and last matching pattern does not start with '!'.
 23///    */
 24///   fileMatch: string[];
 25///   /**
 26///    * If provided, the association is only used if the validated document is located in the given folder (directly or in a subfolder)
 27///    */
 28///   folderUri?: string;
 29///   /*
 30///    * The schema for the given URI.
 31///    * If no schema is provided, the schema will be fetched with the schema request service (if available).
 32///    */
 33///   schema?: JSONSchema;
 34/// }
 35#[derive(Serialize, Deserialize, Debug, Clone)]
 36#[serde(rename_all = "camelCase")]
 37pub struct SchemaAssociation {
 38    pub uri: String,
 39    pub file_match: Vec<String>,
 40    pub folder_uri: Option<String>,
 41    pub schema: Option<serde_json::Value>,
 42}
 43
 44impl lsp::notification::Notification for SchemaAssociationsNotification {
 45    type Params = Vec<SchemaAssociation>;
 46    const METHOD: &'static str = "json/schemaAssociations";
 47}
 48
 49pub fn send_schema_associations_notification(
 50    project: Entity<Project>,
 51    buffer: Entity<Buffer>,
 52    schema_associations: &Vec<SchemaAssociation>,
 53    cx: &mut App,
 54) {
 55    let lsp_store = project.read(cx).lsp_store();
 56    lsp_store.update(cx, |lsp_store, cx| {
 57        let Some(local) = lsp_store.as_local_mut() else {
 58            return;
 59        };
 60        buffer.update(cx, |buffer, cx| {
 61            for (adapter, server) in local
 62                .language_servers_for_buffer(buffer, cx)
 63                .map(|(a, b)| (a.clone(), b.clone()))
 64                .collect::<Vec<_>>()
 65            {
 66                if dbg!(!adapter.adapter.is_primary_zed_json_schema_adapter()) {
 67                    continue;
 68                }
 69
 70                server
 71                    .notify::<SchemaAssociationsNotification>(schema_associations)
 72                    .log_err(); // todo! don't ignore error
 73
 74                let file = match worktree::File::from_dyn(buffer.file()) {
 75                    Some(file) => file,
 76                    None => continue,
 77                };
 78                let language = match buffer.language() {
 79                    Some(language) => language,
 80                    None => continue,
 81                };
 82                let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
 83
 84                let versions = local
 85                    .buffer_snapshots
 86                    .entry(buffer.remote_id())
 87                    .or_default()
 88                    .entry(server.server_id())
 89                    // .and_modify(|_| {
 90                    //     assert!(
 91                    //         false,
 92                    //         "There should not be an existing snapshot for a newly inserted buffer"
 93                    //     )
 94                    // })
 95                    .or_insert_with(|| {
 96                        vec![crate::lsp_store::LspBufferSnapshot {
 97                            version: 0,
 98                            snapshot: buffer.text_snapshot(),
 99                        }]
100                    });
101
102                let snapshot = versions.last().unwrap();
103                let version = snapshot.version;
104                let initial_snapshot = &snapshot.snapshot;
105
106                // if file.worktree.read(cx).id() != key.0
107                //     || !self
108                //         .languages
109                //         .lsp_adapters(&language.name())
110                //         .iter()
111                //         .any(|a| a.name == key.1)
112                // {
113                //     continue;
114                // }
115
116                // didOpen
117                let file = match file.as_local() {
118                    Some(file) => file,
119                    None => continue,
120                };
121                let Some(_) = server
122                    .notify::<lsp::notification::DidCloseTextDocument>(
123                        &DidCloseTextDocumentParams {
124                            text_document: lsp::TextDocumentIdentifier { uri: uri.clone() },
125                        },
126                    )
127                    .log_err()
128                else {
129                    continue;
130                };
131
132                let initial_text = buffer.text();
133
134                server
135                    .notify::<lsp::notification::DidOpenTextDocument>(&DidOpenTextDocumentParams {
136                        text_document: lsp::TextDocumentItem::new(
137                            uri,
138                            adapter.language_id(&language.name()),
139                            version,
140                            initial_text,
141                        ),
142                    })
143                    .log_err();
144            }
145        })
146    })
147}