Add support for building a Tree-sitter grammar at a given path (#9965)

Marshall Bowers created

This PR extends the extension builder—and by extension, the
`zed-extension` CLI—with support for building a Tree-sitter grammar at a
given path within the grammar repository.

Some Tree-sitter grammar repos contain multiple grammars inside of them.
For instance,
[`tree-sitter-php`](https://github.com/tree-sitter/tree-sitter-php/tree/29838ad107f50b1f5f51a0beefa9c9d834fce2b3)
has subfolders for `php` and `php_only`.

The grammar entries in `extension.toml` can now have an optional `path`
field that will be interpreted relative to the root of the grammar
repository:

```toml
[grammars.php]
repository = "https://github.com/tree-sitter/tree-sitter-php"
commit = "8ab93274065cbaf529ea15c24360cfa3348ec9e4"
path = "php"
```

This was something we supported in the old extension packaging script,
but hadn't yet carried it over when we built the new extension builder.

Release Notes:

- N/A

Change summary

crates/extension/src/extension_builder.rs  | 11 ++++++++++-
crates/extension/src/extension_manifest.rs |  2 ++
2 files changed, 12 insertions(+), 1 deletion(-)

Detailed changes

crates/extension/src/extension_builder.rs 🔗

@@ -195,7 +195,13 @@ impl ExtensionBuilder {
             &grammar_metadata.rev,
         )?;
 
-        let src_path = grammar_repo_dir.join("src");
+        let base_grammar_path = grammar_metadata
+            .path
+            .as_ref()
+            .map(|path| grammar_repo_dir.join(path))
+            .unwrap_or(grammar_repo_dir);
+
+        let src_path = base_grammar_path.join("src");
         let parser_path = src_path.join("parser.c");
         let scanner_path = src_path.join("scanner.c");
 
@@ -533,6 +539,8 @@ fn populate_defaults(manifest: &mut ExtensionManifest, extension_path: &Path) ->
                     struct GrammarConfigToml {
                         pub repository: String,
                         pub commit: String,
+                        #[serde(default)]
+                        pub path: Option<String>,
                     }
 
                     let grammar_config = fs::read_to_string(&grammar_path)?;
@@ -548,6 +556,7 @@ fn populate_defaults(manifest: &mut ExtensionManifest, extension_path: &Path) ->
                             GrammarManifestEntry {
                                 repository: grammar_config.repository,
                                 rev: grammar_config.commit,
+                                path: grammar_config.path,
                             },
                         );
                     }

crates/extension/src/extension_manifest.rs 🔗

@@ -92,6 +92,8 @@ pub struct GrammarManifestEntry {
     pub repository: String,
     #[serde(alias = "commit")]
     pub rev: String,
+    #[serde(default)]
+    pub path: Option<String>,
 }
 
 #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]