code-editor: Update CodeEditor to support external language. (#928)
Add [Navi](https://navi-lang.org) language for example to external language for CodeEditor. <img width="864" alt="image" src="https://github.com/user-attachments/assets/73afa56d-31bd-4867-a9f2-0a8cf790af0f" />
This commit is contained in:
parent
44ec74f1fb
commit
c3ec1deb97
12 changed files with 262 additions and 103 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -6772,6 +6772,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"tree-sitter-navi",
|
||||||
"unindent",
|
"unindent",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -7706,6 +7707,17 @@ dependencies = [
|
||||||
"tree-sitter-language",
|
"tree-sitter-language",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-navi"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff190c235d2c0106d8d4c567e990f7776b80a2643b7d201a672cae308675424d"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"tree-sitter",
|
||||||
|
"tree-sitter-language",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tree-sitter-proto"
|
name = "tree-sitter-proto"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
name = "gpui-component-macros"
|
name = "gpui-component-macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
unindent = "0.2.3"
|
unindent = "0.2.3"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
tree-sitter-navi = "0.2.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
gtk = { version = "0.18" }
|
gtk = { version = "0.18" }
|
||||||
|
|
|
||||||
|
|
@ -3,38 +3,87 @@ use gpui_component::{
|
||||||
checkbox::Checkbox,
|
checkbox::Checkbox,
|
||||||
dropdown::{Dropdown, DropdownEvent, DropdownState},
|
dropdown::{Dropdown, DropdownEvent, DropdownState},
|
||||||
h_flex,
|
h_flex,
|
||||||
highlighter::Language,
|
highlighter::{Language, LanguageConfig, LanguageRegistry},
|
||||||
input::{InputEvent, InputState, Marker, TabSize, TextInput},
|
input::{InputEvent, InputState, Marker, TabSize, TextInput},
|
||||||
v_flex,
|
v_flex,
|
||||||
};
|
};
|
||||||
use story::Assets;
|
use story::Assets;
|
||||||
|
|
||||||
|
fn init(cx: &mut App) {
|
||||||
|
LanguageRegistry::global_mut(cx).register(
|
||||||
|
"navi",
|
||||||
|
&LanguageConfig::new(
|
||||||
|
tree_sitter_navi::LANGUAGE.into(),
|
||||||
|
vec![],
|
||||||
|
tree_sitter_navi::HIGHLIGHTS_QUERY,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Example {
|
pub struct Example {
|
||||||
input_state: Entity<InputState>,
|
input_state: Entity<InputState>,
|
||||||
language_state: Entity<DropdownState<Vec<SharedString>>>,
|
language_state: Entity<DropdownState<Vec<SharedString>>>,
|
||||||
language: Language,
|
language: Lang,
|
||||||
line_number: bool,
|
line_number: bool,
|
||||||
need_update: bool,
|
need_update: bool,
|
||||||
_subscribes: Vec<Subscription>,
|
_subscribes: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const LANGUAGES: [(Language, &'static str); 8] = [
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
(Language::Rust, include_str!("./fixtures/test.rs")),
|
enum Lang {
|
||||||
(Language::JavaScript, include_str!("./fixtures/test.js")),
|
BuiltIn(Language),
|
||||||
(Language::Go, include_str!("./fixtures/test.go")),
|
External(&'static str),
|
||||||
(Language::Python, include_str!("./fixtures/test.py")),
|
}
|
||||||
(Language::Ruby, include_str!("./fixtures/test.rb")),
|
|
||||||
(Language::Zig, include_str!("./fixtures/test.zig")),
|
impl Lang {
|
||||||
(Language::C, include_str!("./fixtures/test.c")),
|
fn name(&self) -> &str {
|
||||||
(Language::Sql, include_str!("./fixtures/test.sql")),
|
match self {
|
||||||
|
Lang::BuiltIn(lang) => lang.name(),
|
||||||
|
Lang::External(lang) => lang,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LANGUAGES: [(Lang, &'static str); 8] = [
|
||||||
|
(
|
||||||
|
Lang::BuiltIn(Language::Rust),
|
||||||
|
include_str!("./fixtures/test.rs"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::BuiltIn(Language::JavaScript),
|
||||||
|
include_str!("./fixtures/test.js"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::BuiltIn(Language::Go),
|
||||||
|
include_str!("./fixtures/test.go"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::BuiltIn(Language::Python),
|
||||||
|
include_str!("./fixtures/test.py"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::BuiltIn(Language::Ruby),
|
||||||
|
include_str!("./fixtures/test.rb"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::BuiltIn(Language::Zig),
|
||||||
|
include_str!("./fixtures/test.zig"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::BuiltIn(Language::Sql),
|
||||||
|
include_str!("./fixtures/test.sql"),
|
||||||
|
),
|
||||||
|
(Lang::External("navi"), include_str!("./fixtures/test.nv")),
|
||||||
];
|
];
|
||||||
|
|
||||||
impl Example {
|
impl Example {
|
||||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let default_language = LANGUAGES[0];
|
let default_language = LANGUAGES[0].clone();
|
||||||
let input_state = cx.new(|cx| {
|
let input_state = cx.new(|cx| {
|
||||||
InputState::new(window, cx)
|
InputState::new(window, cx)
|
||||||
.code_editor(default_language.0.name())
|
.code_editor(default_language.0.name().to_string())
|
||||||
.line_number(true)
|
.line_number(true)
|
||||||
.tab_size(TabSize {
|
.tab_size(TabSize {
|
||||||
tab_size: 4,
|
tab_size: 4,
|
||||||
|
|
@ -61,9 +110,11 @@ impl Example {
|
||||||
|this, state, _: &DropdownEvent<Vec<SharedString>>, cx| {
|
|this, state, _: &DropdownEvent<Vec<SharedString>>, cx| {
|
||||||
if let Some(val) = state.read(cx).selected_value() {
|
if let Some(val) = state.read(cx).selected_value() {
|
||||||
if let Some(language) = Language::from_str(&val) {
|
if let Some(language) = Language::from_str(&val) {
|
||||||
this.language = language;
|
this.language = Lang::BuiltIn(language);
|
||||||
this.need_update = true;
|
} else {
|
||||||
|
this.language = Lang::External("navi");
|
||||||
}
|
}
|
||||||
|
this.need_update = true;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -108,8 +159,8 @@ impl Example {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let language = self.language;
|
let language = self.language.name().to_string();
|
||||||
let code = LANGUAGES.iter().find(|s| s.0 == language).unwrap().1;
|
let code = LANGUAGES.iter().find(|s| s.0.name() == language).unwrap().1;
|
||||||
self.input_state.update(cx, |state, cx| {
|
self.input_state.update(cx, |state, cx| {
|
||||||
state.set_value(code, window, cx);
|
state.set_value(code, window, cx);
|
||||||
state.set_highlighter(language, cx);
|
state.set_highlighter(language, cx);
|
||||||
|
|
@ -166,6 +217,7 @@ fn main() {
|
||||||
|
|
||||||
app.run(move |cx| {
|
app.run(move |cx| {
|
||||||
story::init(cx);
|
story::init(cx);
|
||||||
|
init(cx);
|
||||||
cx.activate(true);
|
cx.activate(true);
|
||||||
|
|
||||||
story::create_new_window("Code Editor", Example::view, cx);
|
story::create_new_window("Code Editor", Example::view, cx);
|
||||||
|
|
|
||||||
18
crates/story/examples/fixtures/test.nv
Normal file
18
crates/story/examples/fixtures/test.nv
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use std.net.http.client.{HttpClient, Request};
|
||||||
|
use std.net.http.OK;
|
||||||
|
|
||||||
|
fn main() throws {
|
||||||
|
let client = HttpClient.new(
|
||||||
|
max_redirect_count: 5,
|
||||||
|
user_agent: "navi-client",
|
||||||
|
);
|
||||||
|
let req = try Request.get("https://httpbin.org/get");
|
||||||
|
let res = try client.request(req);
|
||||||
|
|
||||||
|
if (res.status() != OK) {
|
||||||
|
try println("Failed to fetch repo", res.text());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try println(res.text());
|
||||||
|
}
|
||||||
|
|
@ -9,16 +9,17 @@ use tree_sitter::{
|
||||||
};
|
};
|
||||||
use tree_sitter_highlight::{HighlightConfiguration, Highlighter};
|
use tree_sitter_highlight::{HighlightConfiguration, Highlighter};
|
||||||
|
|
||||||
|
use crate::highlighter::LanguageRegistry;
|
||||||
|
|
||||||
use super::{HighlightTheme, Language};
|
use super::{HighlightTheme, Language};
|
||||||
|
|
||||||
/// A syntax highlighter that supports incremental parsing, multiline text,
|
/// A syntax highlighter that supports incremental parsing, multiline text,
|
||||||
/// and caching of highlight results.
|
/// and caching of highlight results.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub struct SyntaxHighlighter {
|
pub struct SyntaxHighlighter {
|
||||||
language_name: &'static str,
|
language: SharedString,
|
||||||
language: Option<Language>,
|
|
||||||
query: Option<Query>,
|
query: Option<Query>,
|
||||||
injection_queries: HashMap<&'static str, Query>,
|
injection_queries: HashMap<SharedString, Query>,
|
||||||
parser: Parser,
|
parser: Parser,
|
||||||
old_tree: Option<Tree>,
|
old_tree: Option<Tree>,
|
||||||
text: SharedString,
|
text: SharedString,
|
||||||
|
|
@ -47,19 +48,22 @@ pub struct SyntaxHighlighter {
|
||||||
|
|
||||||
impl SyntaxHighlighter {
|
impl SyntaxHighlighter {
|
||||||
/// Create a new SyntaxHighlighter for HTML.
|
/// Create a new SyntaxHighlighter for HTML.
|
||||||
pub fn new(lang: &str) -> Self {
|
pub fn new(lang: &str, cx: &App) -> Self {
|
||||||
Self::build_combined_injections_query(&lang).unwrap()
|
Self::build_combined_injections_query(&lang, cx).unwrap_or_else(|| panic!(
|
||||||
|
"failed to build language {}, please make sure have registered the language in LanguageRegistry",
|
||||||
|
lang
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the combined injections query for the given language.
|
/// Build the combined injections query for the given language.
|
||||||
///
|
///
|
||||||
/// https://github.com/tree-sitter/tree-sitter/blob/v0.25.5/highlight/src/lib.rs#L336
|
/// https://github.com/tree-sitter/tree-sitter/blob/v0.25.5/highlight/src/lib.rs#L336
|
||||||
fn build_combined_injections_query(lang: &str) -> Option<Self> {
|
fn build_combined_injections_query(lang: &str, cx: &App) -> Option<Self> {
|
||||||
let language = Language::from_str(&lang);
|
let registry = LanguageRegistry::global(cx);
|
||||||
let Some(language) = language else {
|
let config = registry.language(&lang);
|
||||||
|
let Some(config) = config else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let config = language.config();
|
|
||||||
|
|
||||||
let mut parser = Parser::new();
|
let mut parser = Parser::new();
|
||||||
_ = parser.set_language(&config.language);
|
_ = parser.set_language(&config.language);
|
||||||
|
|
@ -146,19 +150,19 @@ impl SyntaxHighlighter {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut injection_queries = HashMap::new();
|
let mut injection_queries = HashMap::new();
|
||||||
for inj_language in language.injection_languages() {
|
for inj_language in config.injection_languages.iter() {
|
||||||
let inj_config = inj_language.config();
|
if let Some(inj_config) = registry.language(&inj_language) {
|
||||||
|
match Query::new(&inj_config.language, &inj_config.highlights) {
|
||||||
match Query::new(&inj_config.language, &inj_config.highlights) {
|
Ok(q) => {
|
||||||
Ok(q) => {
|
injection_queries.insert(inj_config.name.clone(), q);
|
||||||
injection_queries.insert(inj_language.name(), q);
|
}
|
||||||
}
|
Err(e) => {
|
||||||
Err(e) => {
|
tracing::error!(
|
||||||
tracing::error!(
|
"failed to build injection query for {:?}: {:?}",
|
||||||
"failed to build injection query for {:?}: {:?}",
|
inj_config.name,
|
||||||
inj_language,
|
e
|
||||||
e
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -166,8 +170,7 @@ impl SyntaxHighlighter {
|
||||||
// let highlight_indices = vec![None; query.capture_names().len()];
|
// let highlight_indices = vec![None; query.capture_names().len()];
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
language_name: language.name(),
|
language: config.name.clone(),
|
||||||
language: Some(language),
|
|
||||||
query: Some(query),
|
query: Some(query),
|
||||||
injection_queries,
|
injection_queries,
|
||||||
parser,
|
parser,
|
||||||
|
|
@ -188,21 +191,28 @@ impl SyntaxHighlighter {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_language(&mut self, lang: impl Into<SharedString>) {
|
pub fn set_language(&mut self, language: impl Into<SharedString>, cx: &App) {
|
||||||
let lang = lang.into();
|
let language = language.into();
|
||||||
let language = Language::from_str(&lang);
|
|
||||||
if self.language == language {
|
if self.language == language {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: use build_combined_injections_query to build the query.
|
// FIXME: use build_combined_injections_query to build the query.
|
||||||
|
self.query = None;
|
||||||
if let Some(language) = language {
|
if let Some(language) = Language::from_str(&language) {
|
||||||
|
self.query = Some(language.query());
|
||||||
_ = self.parser.set_language(&language.config().language);
|
_ = self.parser.set_language(&language.config().language);
|
||||||
|
} else {
|
||||||
|
if let Some(config) = LanguageRegistry::global(cx).language(&language) {
|
||||||
|
_ = self.parser.set_language(&config.language);
|
||||||
|
if let Ok(query) = tree_sitter::Query::new(&config.language, &config.highlights) {
|
||||||
|
self.query = Some(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.language = language;
|
self.language = language;
|
||||||
self.query = language.map(|l| l.query());
|
|
||||||
self.old_tree = None;
|
self.old_tree = None;
|
||||||
self.text = SharedString::new("");
|
self.text = SharedString::new("");
|
||||||
self.highlighter = Highlighter::new();
|
self.highlighter = Highlighter::new();
|
||||||
|
|
@ -464,21 +474,26 @@ impl SyntaxHighlighter {
|
||||||
/// - `include_children`: Whether to include the children of the content node.
|
/// - `include_children`: Whether to include the children of the content node.
|
||||||
fn injection_for_match<'a>(
|
fn injection_for_match<'a>(
|
||||||
&self,
|
&self,
|
||||||
parent_name: Option<&'a str>,
|
parent_name: Option<SharedString>,
|
||||||
query: &'a Query,
|
query: &'a Query,
|
||||||
query_match: &QueryMatch<'a, 'a>,
|
query_match: &QueryMatch<'a, 'a>,
|
||||||
source: &'a [u8],
|
source: &'a [u8],
|
||||||
) -> (Option<&'a str>, Option<Node<'a>>, bool) {
|
) -> (Option<SharedString>, Option<Node<'a>>, bool) {
|
||||||
let content_capture_index = self.injection_content_capture_index;
|
let content_capture_index = self.injection_content_capture_index;
|
||||||
let language_capture_index = self.injection_language_capture_index;
|
let language_capture_index = self.injection_language_capture_index;
|
||||||
|
|
||||||
let mut language_name = None;
|
let mut language_name: Option<SharedString> = None;
|
||||||
let mut content_node = None;
|
let mut content_node = None;
|
||||||
|
|
||||||
for capture in query_match.captures {
|
for capture in query_match.captures {
|
||||||
let index = Some(capture.index);
|
let index = Some(capture.index);
|
||||||
if index == language_capture_index {
|
if index == language_capture_index {
|
||||||
language_name = capture.node.utf8_text(source).ok();
|
language_name = capture
|
||||||
|
.node
|
||||||
|
.utf8_text(source)
|
||||||
|
.ok()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.map(SharedString::from);
|
||||||
} else if index == content_capture_index {
|
} else if index == content_capture_index {
|
||||||
content_node = Some(capture.node);
|
content_node = Some(capture.node);
|
||||||
}
|
}
|
||||||
|
|
@ -496,7 +511,8 @@ impl SyntaxHighlighter {
|
||||||
.value
|
.value
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(std::convert::AsRef::as_ref)
|
.map(std::convert::AsRef::as_ref)
|
||||||
.to_owned();
|
.map(ToString::to_string)
|
||||||
|
.map(SharedString::from);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -505,7 +521,7 @@ impl SyntaxHighlighter {
|
||||||
// layer.
|
// layer.
|
||||||
"injection.self" => {
|
"injection.self" => {
|
||||||
if language_name.is_none() {
|
if language_name.is_none() {
|
||||||
language_name = Some(self.language_name);
|
language_name = Some(self.language.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -514,7 +530,7 @@ impl SyntaxHighlighter {
|
||||||
// parent layer
|
// parent layer
|
||||||
"injection.parent" => {
|
"injection.parent" => {
|
||||||
if language_name.is_none() {
|
if language_name.is_none() {
|
||||||
language_name = parent_name;
|
language_name = parent_name.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,9 @@ pub enum Language {
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct LanguageConfig {
|
pub struct LanguageConfig {
|
||||||
|
pub name: SharedString,
|
||||||
pub language: tree_sitter::Language,
|
pub language: tree_sitter::Language,
|
||||||
|
pub injection_languages: Vec<SharedString>,
|
||||||
pub highlights: SharedString,
|
pub highlights: SharedString,
|
||||||
pub injections: SharedString,
|
pub injections: SharedString,
|
||||||
pub locals: SharedString,
|
pub locals: SharedString,
|
||||||
|
|
@ -47,12 +49,15 @@ pub struct LanguageConfig {
|
||||||
impl LanguageConfig {
|
impl LanguageConfig {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
language: tree_sitter::Language,
|
language: tree_sitter::Language,
|
||||||
|
injection_languages: Vec<SharedString>,
|
||||||
highlights: &str,
|
highlights: &str,
|
||||||
injections: &str,
|
injections: &str,
|
||||||
locals: &str,
|
locals: &str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
name: language.name().unwrap_or_default().into(),
|
||||||
language,
|
language,
|
||||||
|
injection_languages,
|
||||||
highlights: SharedString::from(highlights.to_string()),
|
highlights: SharedString::from(highlights.to_string()),
|
||||||
injections: SharedString::from(injections.to_string()),
|
injections: SharedString::from(injections.to_string()),
|
||||||
locals: SharedString::from(locals.to_string()),
|
locals: SharedString::from(locals.to_string()),
|
||||||
|
|
@ -144,14 +149,17 @@ impl Language {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub(super) fn injection_languages(&self) -> Vec<Self> {
|
pub(super) fn injection_languages(&self) -> Vec<SharedString> {
|
||||||
match self {
|
match self {
|
||||||
Self::Markdown => vec![Self::MarkdownInline, Self::Html, Self::Toml, Self::Yaml],
|
Self::Markdown => vec!["markdown-inline", "html", "toml", "yaml"],
|
||||||
Self::MarkdownInline => vec![],
|
Self::MarkdownInline => vec![],
|
||||||
Self::Html => vec![Self::JavaScript, Self::Css],
|
Self::Html => vec!["javascript", "css"],
|
||||||
Self::Rust => vec![Self::Rust],
|
Self::Rust => vec!["rust"],
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
}
|
}
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.into())
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn query(&self) -> Query {
|
pub(super) fn query(&self) -> Query {
|
||||||
|
|
@ -329,7 +337,13 @@ impl Language {
|
||||||
|
|
||||||
let language = tree_sitter::Language::new(language);
|
let language = tree_sitter::Language::new(language);
|
||||||
|
|
||||||
LanguageConfig::new(language, query, injection, locals)
|
LanguageConfig::new(
|
||||||
|
language,
|
||||||
|
self.injection_languages(),
|
||||||
|
query,
|
||||||
|
injection,
|
||||||
|
locals,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,16 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::LanguageConfig;
|
use super::LanguageConfig;
|
||||||
use crate::ThemeMode;
|
use crate::{highlighter::languages, ThemeMode};
|
||||||
|
|
||||||
|
pub(super) fn init(cx: &mut App) {
|
||||||
|
let mut register = LanguageRegistry::new();
|
||||||
|
for language in languages::Language::all() {
|
||||||
|
register.register(language.name(), &language.config());
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.set_global(register);
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) const HIGHLIGHT_NAMES: [&str; 40] = [
|
pub(super) const HIGHLIGHT_NAMES: [&str; 40] = [
|
||||||
"attribute",
|
"attribute",
|
||||||
|
|
@ -359,10 +368,6 @@ impl HighlightTheme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
|
||||||
cx.set_global(LanguageRegistry::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Registry for code highlighter languages.
|
/// Registry for code highlighter languages.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LanguageRegistry {
|
pub struct LanguageRegistry {
|
||||||
|
|
@ -394,13 +399,12 @@ impl LanguageRegistry {
|
||||||
self.languages.insert(lang.to_string(), config.clone());
|
self.languages.insert(lang.to_string(), config.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
/// Set highlighter theme.
|
||||||
pub(crate) fn set_theme(&mut self, light_theme: &HighlightTheme, dark_theme: &HighlightTheme) {
|
pub fn set_theme(&mut self, light: &HighlightTheme, dark: &HighlightTheme) {
|
||||||
self.light_theme = Arc::new(light_theme.clone());
|
self.light_theme = Arc::new(light.clone());
|
||||||
self.dark_theme = Arc::new(dark_theme.clone());
|
self.dark_theme = Arc::new(dark.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) fn theme(&self, is_dark: bool) -> &Arc<HighlightTheme> {
|
pub(crate) fn theme(&self, is_dark: bool) -> &Arc<HighlightTheme> {
|
||||||
if is_dark {
|
if is_dark {
|
||||||
&self.dark_theme
|
&self.dark_theme
|
||||||
|
|
@ -408,6 +412,16 @@ impl LanguageRegistry {
|
||||||
&self.light_theme
|
&self.light_theme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the map of registered languages.
|
||||||
|
pub fn languages(&self) -> &HashMap<String, LanguageConfig> {
|
||||||
|
&self.languages
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the language configuration for the given language name.
|
||||||
|
pub fn language(&self, name: &str) -> Option<&LanguageConfig> {
|
||||||
|
self.languages.get(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{highlighter::LanguageRegistry, ActiveTheme as _, Root};
|
use crate::{
|
||||||
|
highlighter::{LanguageRegistry, SyntaxHighlighter},
|
||||||
|
ActiveTheme as _, Root,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{mode::InputMode, InputState, LastLayout};
|
use super::{mode::InputMode, InputState, LastLayout};
|
||||||
|
|
||||||
|
|
@ -363,12 +366,22 @@ impl TextElement {
|
||||||
let theme = LanguageRegistry::global(cx)
|
let theme = LanguageRegistry::global(cx)
|
||||||
.theme(cx.theme().is_dark())
|
.theme(cx.theme().is_dark())
|
||||||
.clone();
|
.clone();
|
||||||
self.input.update(cx, |state, _| match &state.mode {
|
self.input.update(cx, |state, cx| match &state.mode {
|
||||||
InputMode::CodeEditor {
|
InputMode::CodeEditor {
|
||||||
|
language,
|
||||||
highlighter,
|
highlighter,
|
||||||
markers,
|
markers,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
// Init highlighter if not initialized
|
||||||
|
let mut highlighter = highlighter.borrow_mut();
|
||||||
|
if highlighter.is_none() {
|
||||||
|
highlighter.replace(SyntaxHighlighter::new(language, cx));
|
||||||
|
};
|
||||||
|
let Some(highlighter) = highlighter.as_ref() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let mut skipped_offset = 0;
|
let mut skipped_offset = 0;
|
||||||
let mut styles = vec![];
|
let mut styles = vec![];
|
||||||
|
|
@ -386,8 +399,7 @@ impl TextElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let range = offset..offset + line_len;
|
let range = offset..offset + line_len;
|
||||||
let line_styles = highlighter.borrow().styles(&range, &theme);
|
let line_styles = highlighter.styles(&range, &theme);
|
||||||
|
|
||||||
styles = gpui::combine_highlights(styles, line_styles).collect();
|
styles = gpui::combine_highlights(styles, line_styles).collect();
|
||||||
|
|
||||||
offset = range.end;
|
offset = range.end;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::{cell::RefCell, ops::Range};
|
||||||
|
|
||||||
use gpui::{DefiniteLength, SharedString};
|
use gpui::{App, DefiniteLength, SharedString};
|
||||||
|
|
||||||
use crate::{highlighter::SyntaxHighlighter, input::marker::Marker};
|
use crate::{highlighter::SyntaxHighlighter, input::marker::Marker};
|
||||||
|
|
||||||
|
|
@ -49,7 +49,8 @@ pub enum InputMode {
|
||||||
height: Option<DefiniteLength>,
|
height: Option<DefiniteLength>,
|
||||||
/// Show line number
|
/// Show line number
|
||||||
line_number: bool,
|
line_number: bool,
|
||||||
highlighter: Rc<RefCell<SyntaxHighlighter>>,
|
language: SharedString,
|
||||||
|
highlighter: Rc<RefCell<Option<SyntaxHighlighter>>>,
|
||||||
markers: Vec<Marker>,
|
markers: Vec<Marker>,
|
||||||
},
|
},
|
||||||
AutoGrow {
|
AutoGrow {
|
||||||
|
|
@ -154,11 +155,30 @@ impl InputMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
pub(super) fn update_highlighter(
|
||||||
pub(super) fn highlighter(&self) -> Option<&Rc<RefCell<SyntaxHighlighter>>> {
|
&mut self,
|
||||||
|
selected_range: &Range<usize>,
|
||||||
|
full_text: SharedString,
|
||||||
|
new_text: &str,
|
||||||
|
cx: &mut App,
|
||||||
|
) {
|
||||||
match &self {
|
match &self {
|
||||||
InputMode::CodeEditor { highlighter, .. } => Some(highlighter),
|
InputMode::CodeEditor {
|
||||||
_ => None,
|
language,
|
||||||
|
highlighter,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let mut highlighter = highlighter.borrow_mut();
|
||||||
|
if highlighter.is_none() {
|
||||||
|
let new_highlighter = SyntaxHighlighter::new(language, cx);
|
||||||
|
highlighter.replace(new_highlighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(highlighter) = highlighter.as_mut() {
|
||||||
|
highlighter.update(selected_range, full_text, new_text, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ use super::{
|
||||||
number_input,
|
number_input,
|
||||||
text_wrapper::TextWrapper,
|
text_wrapper::TextWrapper,
|
||||||
};
|
};
|
||||||
use crate::highlighter::SyntaxHighlighter;
|
|
||||||
use crate::input::marker::Marker;
|
use crate::input::marker::Marker;
|
||||||
use crate::{history::History, scroll::ScrollbarState, Root};
|
use crate::{history::History, scroll::ScrollbarState, Root};
|
||||||
|
|
||||||
|
|
@ -379,7 +378,8 @@ impl InputState {
|
||||||
self.mode = InputMode::CodeEditor {
|
self.mode = InputMode::CodeEditor {
|
||||||
rows: 2,
|
rows: 2,
|
||||||
tab: TabSize::default(),
|
tab: TabSize::default(),
|
||||||
highlighter: Rc::new(RefCell::new(SyntaxHighlighter::new(&language))),
|
language,
|
||||||
|
highlighter: Rc::new(RefCell::new(None)),
|
||||||
line_number: true,
|
line_number: true,
|
||||||
height: Some(relative(1.)),
|
height: Some(relative(1.)),
|
||||||
markers: vec![],
|
markers: vec![],
|
||||||
|
|
@ -442,11 +442,20 @@ impl InputState {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set highlighter, only for [`InputMode::CodeEditor`] mode.
|
/// Set highlighter language for for [`InputMode::CodeEditor`] mode.
|
||||||
pub fn set_highlighter(&mut self, language: impl Into<SharedString>, cx: &mut Context<Self>) {
|
pub fn set_highlighter(
|
||||||
|
&mut self,
|
||||||
|
new_language: impl Into<SharedString>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
match &mut self.mode {
|
match &mut self.mode {
|
||||||
InputMode::CodeEditor { highlighter, .. } => {
|
InputMode::CodeEditor {
|
||||||
highlighter.borrow_mut().set_language(language);
|
language,
|
||||||
|
highlighter,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
*language = new_language.into();
|
||||||
|
*highlighter.borrow_mut() = None;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -1998,11 +2007,8 @@ impl EntityInputHandler for InputState {
|
||||||
|
|
||||||
self.push_history(&range, &new_text, window, cx);
|
self.push_history(&range, &new_text, window, cx);
|
||||||
self.text = mask_text.clone();
|
self.text = mask_text.clone();
|
||||||
if let Some(highlighter) = self.mode.highlighter() {
|
self.mode
|
||||||
highlighter
|
.update_highlighter(&range, self.text.clone(), &new_text, cx);
|
||||||
.borrow_mut()
|
|
||||||
.update(&range, self.text.clone(), &new_text, cx);
|
|
||||||
}
|
|
||||||
self.mode.clear_markers();
|
self.mode.clear_markers();
|
||||||
self.text_wrapper.update(self.text.clone(), false, cx);
|
self.text_wrapper.update(self.text.clone(), false, cx);
|
||||||
self.selected_range = new_pos..new_pos;
|
self.selected_range = new_pos..new_pos;
|
||||||
|
|
@ -2042,11 +2048,8 @@ impl EntityInputHandler for InputState {
|
||||||
|
|
||||||
self.push_history(&range, new_text, window, cx);
|
self.push_history(&range, new_text, window, cx);
|
||||||
self.text = pending_text;
|
self.text = pending_text;
|
||||||
if let Some(highlighter) = self.mode.highlighter() {
|
self.mode
|
||||||
highlighter
|
.update_highlighter(&range, self.text.clone(), &new_text, cx);
|
||||||
.borrow_mut()
|
|
||||||
.update(&range, self.text.clone(), &new_text, cx);
|
|
||||||
}
|
|
||||||
self.mode.clear_markers();
|
self.mode.clear_markers();
|
||||||
self.text_wrapper.update(self.text.clone(), false, cx);
|
self.text_wrapper.update(self.text.clone(), false, cx);
|
||||||
if new_text.is_empty() {
|
if new_text.is_empty() {
|
||||||
|
|
@ -2151,11 +2154,8 @@ impl Focusable for InputState {
|
||||||
impl Render for InputState {
|
impl Render for InputState {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
self.text_wrapper.update(self.text.clone(), false, cx);
|
self.text_wrapper.update(self.text.clone(), false, cx);
|
||||||
if let Some(highlighter) = self.mode.highlighter() {
|
self.mode
|
||||||
highlighter
|
.update_highlighter(&(0..0), self.text.clone(), "", cx);
|
||||||
.borrow_mut()
|
|
||||||
.update(&(0..0), self.text.clone(), "", cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id("text-element")
|
.id("text-element")
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,7 @@ impl CodeBlock {
|
||||||
.clone();
|
.clone();
|
||||||
let mut styles = vec![];
|
let mut styles = vec![];
|
||||||
if let Some(lang) = &lang {
|
if let Some(lang) = &lang {
|
||||||
let mut highlighter = SyntaxHighlighter::new(&lang);
|
let mut highlighter = SyntaxHighlighter::new(&lang, cx);
|
||||||
highlighter.update(&(0..0), code.clone(), "", cx);
|
highlighter.update(&(0..0), code.clone(), "", cx);
|
||||||
styles = highlighter.styles(&(0..code.len()), &theme);
|
styles = highlighter.styles(&(0..code.len()), &theme);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue