Merge pull request #73 from zed-industries/grammars-cursor

Initial syntax support
This commit is contained in:
Max Brunsfeld
2021-05-28 14:40:14 -07:00
committed by GitHub
38 changed files with 2324 additions and 453 deletions
Generated
+25 -4
View File
@@ -347,7 +347,7 @@ dependencies = [
"tar",
"target_build_utils",
"term",
"toml",
"toml 0.4.10",
"uuid",
"walkdir",
]
@@ -2703,15 +2703,33 @@ dependencies = [
]
[[package]]
name = "tree-sitter"
version = "0.17.1"
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d18dcb776d3affaba6db04d11d645946d34a69b3172e588af96ce9fecd20faac"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde 1.0.125",
]
[[package]]
name = "tree-sitter"
version = "0.19.5"
source = "git+https://github.com/tree-sitter/tree-sitter?rev=d72771a19f4143530b1cfd23808e344f1276e176#d72771a19f4143530b1cfd23808e344f1276e176"
dependencies = [
"cc",
"regex",
]
[[package]]
name = "tree-sitter-rust"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784f7ef9cdbd4c895dc2d4bb785e95b4a5364a602eec803681db83d1927ddf15"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "ttf-parser"
version = "0.9.0"
@@ -2987,5 +3005,8 @@ dependencies = [
"smallvec",
"smol",
"tempdir",
"toml 0.5.8",
"tree-sitter",
"tree-sitter-rust",
"unindent",
]
+6 -5
View File
@@ -2,13 +2,14 @@
members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"]
[patch.crates-io]
async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"}
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "d72771a19f4143530b1cfd23808e344f1276e176" }
# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457
cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
cocoa-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
core-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
core-graphics = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
cocoa = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" }
cocoa-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" }
core-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" }
core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737" }
[profile.dev]
split-debuginfo = "unpacked"
+1 -1
View File
@@ -26,7 +26,7 @@ serde_json = "1.0.64"
smallvec = {version = "1.6", features = ["union"]}
smol = "1.2"
tiny-skia = "0.5"
tree-sitter = "0.17"
tree-sitter = "0.19"
usvg = "0.14"
[build-dependencies]
@@ -0,0 +1,26 @@
[package]
name = "tree-sitter-context-predicate"
description = "context-predicate grammar for the tree-sitter parsing library"
version = "0.0.1"
keywords = ["incremental", "parsing", "context-predicate"]
categories = ["parsing", "text-editors"]
repository = "https://github.com/tree-sitter/tree-sitter-javascript"
edition = "2018"
license = "MIT"
build = "bindings/rust/build.rs"
include = [
"bindings/rust/*",
"grammar.js",
"queries/*",
"src/*",
]
[lib]
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter = "0.19.3"
[build-dependencies]
cc = "1.0"
+1 -1
View File
@@ -8,7 +8,7 @@
],
"sources": [
"src/parser.c",
"src/binding.cc"
"bindings/node/binding.cc"
],
"cflags_c": [
"-std=c99",
@@ -0,0 +1,19 @@
try {
module.exports = require("../../build/Release/tree_sitter_context_predicate_binding");
} catch (error1) {
if (error1.code !== 'MODULE_NOT_FOUND') {
throw error1;
}
try {
module.exports = require("../../build/Debug/tree_sitter_context_predicate_binding");
} catch (error2) {
if (error2.code !== 'MODULE_NOT_FOUND') {
throw error2;
}
throw error1
}
}
try {
module.exports.nodeTypeInfo = require("../../src/node-types.json");
} catch (_) {}
@@ -0,0 +1,40 @@
fn main() {
let src_dir = std::path::Path::new("src");
let mut c_config = cc::Build::new();
c_config.include(&src_dir);
c_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable")
.flag_if_supported("-Wno-trigraphs");
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
// If your language uses an external scanner written in C,
// then include this block of code:
/*
let scanner_path = src_dir.join("scanner.c");
c_config.file(&scanner_path);
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
*/
c_config.compile("parser");
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
// If your language uses an external scanner written in C++,
// then include this block of code:
/*
let mut cpp_config = cc::Build::new();
cpp_config.cpp(true);
cpp_config.include(&src_dir);
cpp_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable");
let scanner_path = src_dir.join("scanner.cc");
cpp_config.file(&scanner_path);
cpp_config.compile("scanner");
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
*/
}
@@ -0,0 +1,52 @@
//! This crate provides context_predicate language support for the [tree-sitter][] parsing library.
//!
//! Typically, you will use the [language][language func] function to add this language to a
//! tree-sitter [Parser][], and then use the parser to parse some code:
//!
//! ```
//! let code = "";
//! let mut parser = tree_sitter::Parser::new();
//! parser.set_language(tree_sitter_context_predicate::language()).expect("Error loading context_predicate grammar");
//! let tree = parser.parse(code, None).unwrap();
//! ```
//!
//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
//! [language func]: fn.language.html
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
//! [tree-sitter]: https://tree-sitter.github.io/
use tree_sitter::Language;
extern "C" {
fn tree_sitter_context_predicate() -> Language;
}
/// Get the tree-sitter [Language][] for this grammar.
///
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
pub fn language() -> Language {
unsafe { tree_sitter_context_predicate() }
}
/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json");
// Uncomment these to include any queries that this grammar contains
// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm");
// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm");
// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
#[cfg(test)]
mod tests {
#[test]
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(super::language())
.expect("Error loading context_predicate language");
}
}
-13
View File
@@ -1,13 +0,0 @@
try {
module.exports = require("./build/Release/tree_sitter_context_predicate_binding");
} catch (error) {
try {
module.exports = require("./build/Debug/tree_sitter_context_predicate_binding");
} catch (_) {
throw error
}
}
try {
module.exports.nodeTypeInfo = require("./src/node-types.json");
} catch (_) { }
+7 -7
View File
@@ -9,7 +9,7 @@
"nan": "^2.14.0"
},
"devDependencies": {
"tree-sitter-cli": "^0.18.3"
"tree-sitter-cli": "^0.19.5"
}
},
"node_modules/nan": {
@@ -18,9 +18,9 @@
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
},
"node_modules/tree-sitter-cli": {
"version": "0.18.3",
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.18.3.tgz",
"integrity": "sha512-ntN8Siljy7dlazb4cSYtZCfibaNppIy6RIr/XGt44GW1hAy/yuTLtKVi4kqYlImB4/7H0AjktcSlQRmI8zLNng==",
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz",
"integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==",
"dev": true,
"hasInstallScript": true,
"bin": {
@@ -35,9 +35,9 @@
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
},
"tree-sitter-cli": {
"version": "0.18.3",
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.18.3.tgz",
"integrity": "sha512-ntN8Siljy7dlazb4cSYtZCfibaNppIy6RIr/XGt44GW1hAy/yuTLtKVi4kqYlImB4/7H0AjktcSlQRmI8zLNng==",
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.19.5.tgz",
"integrity": "sha512-kRzKrUAwpDN9AjA3b0tPBwT1hd8N2oQvvvHup2OEsX6mdsSMLmAvR+NSqK9fe05JrRbVvG8mbteNUQsxlMQohQ==",
"dev": true
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"name": "tree-sitter-context-predicate",
"main": "index.js",
"main": "bindings/node",
"devDependencies": {
"tree-sitter-cli": "^0.18.3"
"tree-sitter-cli": "^0.19.5"
},
"dependencies": {
"nan": "^2.14.0"
+30 -28
View File
@@ -5,7 +5,7 @@
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
#define LANGUAGE_VERSION 12
#define LANGUAGE_VERSION 13
#define STATE_COUNT 18
#define LARGE_STATE_COUNT 6
#define SYMBOL_COUNT 17
@@ -14,6 +14,7 @@
#define EXTERNAL_TOKEN_COUNT 0
#define FIELD_COUNT 3
#define MAX_ALIAS_SEQUENCE_LENGTH 3
#define PRODUCTION_ID_COUNT 3
enum {
sym_identifier = 1,
@@ -34,7 +35,7 @@ enum {
sym_parenthesized = 16,
};
static const char *ts_symbol_names[] = {
static const char * const ts_symbol_names[] = {
[ts_builtin_sym_end] = "end",
[sym_identifier] = "identifier",
[anon_sym_BANG] = "!",
@@ -54,7 +55,7 @@ static const char *ts_symbol_names[] = {
[sym_parenthesized] = "parenthesized",
};
static TSSymbol ts_symbol_map[] = {
static const TSSymbol ts_symbol_map[] = {
[ts_builtin_sym_end] = ts_builtin_sym_end,
[sym_identifier] = sym_identifier,
[anon_sym_BANG] = anon_sym_BANG,
@@ -151,14 +152,14 @@ enum {
field_right = 3,
};
static const char *ts_field_names[] = {
static const char * const ts_field_names[] = {
[0] = NULL,
[field_expression] = "expression",
[field_left] = "left",
[field_right] = "right",
};
static const TSFieldMapSlice ts_field_map_slices[3] = {
static const TSFieldMapSlice ts_field_map_slices[PRODUCTION_ID_COUNT] = {
[1] = {.index = 0, .length = 1},
[2] = {.index = 1, .length = 2},
};
@@ -171,11 +172,11 @@ static const TSFieldMapEntry ts_field_map_entries[] = {
{field_right, 2},
};
static TSSymbol ts_alias_sequences[3][MAX_ALIAS_SEQUENCE_LENGTH] = {
static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT][MAX_ALIAS_SEQUENCE_LENGTH] = {
[0] = {0},
};
static uint16_t ts_non_terminal_alias_map[] = {
static const uint16_t ts_non_terminal_alias_map[] = {
0,
};
@@ -279,7 +280,7 @@ static bool ts_lex(TSLexer *lexer, TSStateId state) {
}
}
static TSLexMode ts_lex_modes[STATE_COUNT] = {
static const TSLexMode ts_lex_modes[STATE_COUNT] = {
[0] = {.lex_state = 0},
[1] = {.lex_state = 1},
[2] = {.lex_state = 1},
@@ -300,7 +301,7 @@ static TSLexMode ts_lex_modes[STATE_COUNT] = {
[17] = {.lex_state = 0},
};
static uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = {
static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = {
[0] = {
[ts_builtin_sym_end] = ACTIONS(1),
[sym_identifier] = ACTIONS(1),
@@ -375,7 +376,7 @@ static uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = {
},
};
static uint16_t ts_small_parse_table[] = {
static const uint16_t ts_small_parse_table[] = {
[0] = 3,
ACTIONS(11), 1,
anon_sym_EQ_EQ,
@@ -448,7 +449,7 @@ static uint16_t ts_small_parse_table[] = {
sym_identifier,
};
static uint32_t ts_small_parse_table_map[] = {
static const uint32_t ts_small_parse_table_map[] = {
[SMALL_STATE(6)] = 0,
[SMALL_STATE(7)] = 13,
[SMALL_STATE(8)] = 20,
@@ -463,7 +464,7 @@ static uint32_t ts_small_parse_table_map[] = {
[SMALL_STATE(17)] = 85,
};
static TSParseActionEntry ts_parse_actions[] = {
static const TSParseActionEntry ts_parse_actions[] = {
[0] = {.entry = {.count = 0, .reusable = false}},
[1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(),
[3] = {.entry = {.count = 1, .reusable = true}}, SHIFT(6),
@@ -495,30 +496,31 @@ extern "C" {
#endif
extern const TSLanguage *tree_sitter_context_predicate(void) {
static TSLanguage language = {
static const TSLanguage language = {
.version = LANGUAGE_VERSION,
.symbol_count = SYMBOL_COUNT,
.alias_count = ALIAS_COUNT,
.token_count = TOKEN_COUNT,
.external_token_count = EXTERNAL_TOKEN_COUNT,
.symbol_names = ts_symbol_names,
.symbol_metadata = ts_symbol_metadata,
.parse_table = (const uint16_t *)ts_parse_table,
.parse_actions = ts_parse_actions,
.lex_modes = ts_lex_modes,
.alias_sequences = (const TSSymbol *)ts_alias_sequences,
.max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH,
.lex_fn = ts_lex,
.field_count = FIELD_COUNT,
.field_map_slices = (const TSFieldMapSlice *)ts_field_map_slices,
.field_map_entries = (const TSFieldMapEntry *)ts_field_map_entries,
.field_names = ts_field_names,
.state_count = STATE_COUNT,
.large_state_count = LARGE_STATE_COUNT,
.small_parse_table = (const uint16_t *)ts_small_parse_table,
.small_parse_table_map = (const uint32_t *)ts_small_parse_table_map,
.production_id_count = PRODUCTION_ID_COUNT,
.field_count = FIELD_COUNT,
.max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH,
.parse_table = &ts_parse_table[0][0],
.small_parse_table = ts_small_parse_table,
.small_parse_table_map = ts_small_parse_table_map,
.parse_actions = ts_parse_actions,
.symbol_names = ts_symbol_names,
.field_names = ts_field_names,
.field_map_slices = ts_field_map_slices,
.field_map_entries = ts_field_map_entries,
.symbol_metadata = ts_symbol_metadata,
.public_symbol_map = ts_symbol_map,
.alias_map = ts_non_terminal_alias_map,
.state_count = STATE_COUNT,
.alias_sequences = &ts_alias_sequences[0][0],
.lex_modes = ts_lex_modes,
.lex_fn = ts_lex,
};
return &language;
}
@@ -13,6 +13,8 @@ extern "C" {
#define ts_builtin_sym_end 0
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
typedef uint16_t TSStateId;
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
@@ -30,12 +32,10 @@ typedef struct {
uint16_t length;
} TSFieldMapSlice;
typedef uint16_t TSStateId;
typedef struct {
bool visible : 1;
bool named : 1;
bool supertype: 1;
bool visible;
bool named;
bool supertype;
} TSSymbolMetadata;
typedef struct TSLexer TSLexer;
@@ -57,21 +57,21 @@ typedef enum {
TSParseActionTypeRecover,
} TSParseActionType;
typedef struct {
union {
struct {
TSStateId state;
bool extra : 1;
bool repetition : 1;
} shift;
struct {
TSSymbol symbol;
int16_t dynamic_precedence;
uint8_t child_count;
uint8_t production_id;
} reduce;
} params;
TSParseActionType type : 4;
typedef union {
struct {
uint8_t type;
TSStateId state;
bool extra;
bool repetition;
} shift;
struct {
uint8_t type;
uint8_t child_count;
TSSymbol symbol;
int16_t dynamic_precedence;
uint16_t production_id;
} reduce;
uint8_t type;
} TSParseAction;
typedef struct {
@@ -83,7 +83,7 @@ typedef union {
TSParseAction action;
struct {
uint8_t count;
bool reusable : 1;
bool reusable;
} entry;
} TSParseActionEntry;
@@ -93,13 +93,24 @@ struct TSLanguage {
uint32_t alias_count;
uint32_t token_count;
uint32_t external_token_count;
const char **symbol_names;
const TSSymbolMetadata *symbol_metadata;
const uint16_t *parse_table;
const TSParseActionEntry *parse_actions;
const TSLexMode *lex_modes;
const TSSymbol *alias_sequences;
uint32_t state_count;
uint32_t large_state_count;
uint32_t production_id_count;
uint32_t field_count;
uint16_t max_alias_sequence_length;
const uint16_t *parse_table;
const uint16_t *small_parse_table;
const uint32_t *small_parse_table_map;
const TSParseActionEntry *parse_actions;
const char * const *symbol_names;
const char * const *field_names;
const TSFieldMapSlice *field_map_slices;
const TSFieldMapEntry *field_map_entries;
const TSSymbolMetadata *symbol_metadata;
const TSSymbol *public_symbol_map;
const uint16_t *alias_map;
const TSSymbol *alias_sequences;
const TSLexMode *lex_modes;
bool (*lex_fn)(TSLexer *, TSStateId);
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
TSSymbol keyword_capture_token;
@@ -112,16 +123,6 @@ struct TSLanguage {
unsigned (*serialize)(void *, char *);
void (*deserialize)(void *, const char *, unsigned);
} external_scanner;
uint32_t field_count;
const TSFieldMapSlice *field_map_slices;
const TSFieldMapEntry *field_map_entries;
const char **field_names;
uint32_t large_state_count;
const uint16_t *small_parse_table;
const uint32_t *small_parse_table_map;
const TSSymbol *public_symbol_map;
const uint16_t *alias_map;
uint32_t state_count;
};
/*
@@ -170,66 +171,50 @@ struct TSLanguage {
#define ACTIONS(id) id
#define SHIFT(state_value) \
{ \
{ \
.params = { \
.shift = { \
.state = state_value \
} \
}, \
.type = TSParseActionTypeShift \
} \
}
#define SHIFT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = state_value \
} \
}}
#define SHIFT_REPEAT(state_value) \
{ \
{ \
.params = { \
.shift = { \
.state = state_value, \
.repetition = true \
} \
}, \
.type = TSParseActionTypeShift \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = state_value, \
.repetition = true \
} \
}
#define RECOVER() \
{ \
{ .type = TSParseActionTypeRecover } \
}
}}
#define SHIFT_EXTRA() \
{ \
{ \
.params = { \
.shift = { \
.extra = true \
} \
}, \
.type = TSParseActionTypeShift \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.extra = true \
} \
}
}}
#define REDUCE(symbol_val, child_count_val, ...) \
{ \
{ \
.params = { \
.reduce = { \
.symbol = symbol_val, \
.child_count = child_count_val, \
__VA_ARGS__ \
}, \
}, \
.type = TSParseActionTypeReduce \
} \
}
{{ \
.reduce = { \
.type = TSParseActionTypeReduce, \
.symbol = symbol_val, \
.child_count = child_count_val, \
__VA_ARGS__ \
}, \
}}
#define ACCEPT_INPUT() \
{ \
{ .type = TSParseActionTypeAccept } \
}
#define RECOVER() \
{{ \
.type = TSParseActionTypeRecover \
}}
#define ACCEPT_INPUT() \
{{ \
.type = TSParseActionTypeAccept \
}}
#ifdef __cplusplus
}
+26
View File
@@ -161,3 +161,29 @@ impl FontCache {
metric * font_size / self.metric(font_id, |m| m.units_per_em as f32)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
fonts::{Style, Weight},
platform::{test, Platform as _},
};
#[test]
fn test_select_font() {
let platform = test::platform();
let fonts = FontCache::new(platform.fonts());
let arial = fonts.load_family(&["Arial"]).unwrap();
let arial_regular = fonts.select_font(arial, &Properties::new()).unwrap();
let arial_italic = fonts
.select_font(arial, &Properties::new().style(Style::Italic))
.unwrap();
let arial_bold = fonts
.select_font(arial, &Properties::new().weight(Weight::BOLD))
.unwrap();
assert_ne!(arial_regular, arial_italic);
assert_ne!(arial_regular, arial_bold);
assert_ne!(arial_italic, arial_bold);
}
}
+28 -24
View File
@@ -322,6 +322,9 @@ mod tests {
let menlo_regular = fonts.select_font(&menlo, &Properties::new())?;
let menlo_italic = fonts.select_font(&menlo, &Properties::new().style(Style::Italic))?;
let menlo_bold = fonts.select_font(&menlo, &Properties::new().weight(Weight::BOLD))?;
assert_ne!(menlo_regular, menlo_italic);
assert_ne!(menlo_regular, menlo_bold);
assert_ne!(menlo_italic, menlo_bold);
let line = fonts.layout_str(
"hello world",
@@ -371,32 +374,33 @@ mod tests {
Ok(())
}
// #[test]
// fn test_rasterize_glyph() {
// use std::{fs::File, io::BufWriter, path::Path};
#[test]
#[ignore]
fn test_rasterize_glyph() {
use std::{fs::File, io::BufWriter, path::Path};
// let fonts = FontSystem::new();
// let font_ids = fonts.load_family("Fira Code").unwrap();
// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
// let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
let fonts = FontSystem::new();
let font_ids = fonts.load_family("Fira Code").unwrap();
let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
// const VARIANTS: usize = 1;
// for i in 0..VARIANTS {
// let variant = i as f32 / VARIANTS as f32;
// let (bounds, bytes) = fonts
// .rasterize_glyph(font_id, 16.0, glyph_id, vec2f(variant, variant), 2.)
// .unwrap();
const VARIANTS: usize = 1;
for i in 0..VARIANTS {
let variant = i as f32 / VARIANTS as f32;
let (bounds, bytes) = fonts
.rasterize_glyph(font_id, 16.0, glyph_id, vec2f(variant, variant), 2.)
.unwrap();
// let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
// let path = Path::new(&name);
// let file = File::create(path).unwrap();
// let ref mut w = BufWriter::new(file);
let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
let path = Path::new(&name);
let file = File::create(path).unwrap();
let ref mut w = BufWriter::new(file);
// let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
// encoder.set_color(png::ColorType::Grayscale);
// encoder.set_depth(png::BitDepth::Eight);
// let mut writer = encoder.write_header().unwrap();
// writer.write_image_data(&bytes).unwrap();
// }
// }
let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
encoder.set_color(png::ColorType::Grayscale);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().unwrap();
writer.write_image_data(&bytes).unwrap();
}
}
}
+3
View File
@@ -38,6 +38,9 @@ similar = "1.3"
simplelog = "0.9"
smallvec = {version = "1.6", features = ["union"]}
smol = "1.2.5"
toml = "0.5"
tree-sitter = "0.19.5"
tree-sitter-rust = "0.19.0"
[dev-dependencies]
cargo-bundle = "0.5.0"
+13
View File
@@ -0,0 +1,13 @@
[ui]
background = 0xffffff
line_numbers = 0x237791
text = 0x0d0d0d
[syntax]
keyword = 0xaf00db
function = 0x795e26
string = 0xa31515
type = 0x267599
number = 0x0d885b
comment = 0x048204
property = 0x001080
+6
View File
@@ -0,0 +1,6 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
(closure_parameters "|" @open "|" @close)
+8
View File
@@ -0,0 +1,8 @@
name = "Rust"
path_suffixes = ["rs"]
bracket_pairs = [
{ start = "{", end = "}" },
{ start = "[", end = "]" },
{ start = "(", end = ")" },
{ start = "<", end = ">" },
]
+63
View File
@@ -0,0 +1,63 @@
(type_identifier) @type
(primitive_type) @type.builtin
(field_identifier) @property
(call_expression
function: [
(identifier) @function
(scoped_identifier
name: (identifier) @function)
(field_expression
field: (field_identifier) @function.method)
])
(function_item name: (identifier) @function.definition)
(function_signature_item name: (identifier) @function.definition)
[
"async"
"break"
"const"
"continue"
"default"
"dyn"
"else"
"enum"
"extern"
"for"
"fn"
"if"
"in"
"impl"
"let"
"loop"
"macro_rules!"
"match"
"mod"
"move"
"pub"
"return"
"static"
"struct"
"trait"
"type"
"use"
"where"
"while"
"union"
"unsafe"
(mutable_specifier)
(super)
] @keyword
[
(string_literal)
(raw_string_literal)
(char_literal)
] @string
[
(line_comment)
(block_comment)
] @comment
File diff suppressed because it is too large Load Diff
+23
View File
@@ -10,6 +10,11 @@ pub struct Point {
}
impl Point {
pub const MAX: Self = Self {
row: u32::MAX,
column: u32::MAX,
};
pub fn new(row: u32, column: u32) -> Self {
Point { row, column }
}
@@ -98,3 +103,21 @@ impl Ord for Point {
}
}
}
impl Into<tree_sitter::Point> for Point {
fn into(self) -> tree_sitter::Point {
tree_sitter::Point {
row: self.row as usize,
column: self.column as usize,
}
}
}
impl From<tree_sitter::Point> for Point {
fn from(point: tree_sitter::Point) -> Self {
Self {
row: point.row as u32,
column: point.column as u32,
}
}
}
+10 -8
View File
@@ -118,12 +118,12 @@ impl Rope {
self.chunks_in_range(start..self.len()).flat_map(str::chars)
}
pub fn chunks<'a>(&'a self) -> ChunksIter<'a> {
pub fn chunks<'a>(&'a self) -> Chunks<'a> {
self.chunks_in_range(0..self.len())
}
pub fn chunks_in_range<'a>(&'a self, range: Range<usize>) -> ChunksIter<'a> {
ChunksIter::new(self, range)
pub fn chunks_in_range<'a>(&'a self, range: Range<usize>) -> Chunks<'a> {
Chunks::new(self, range)
}
pub fn to_point(&self, offset: usize) -> Point {
@@ -268,12 +268,12 @@ impl<'a> Cursor<'a> {
}
}
pub struct ChunksIter<'a> {
pub struct Chunks<'a> {
chunks: sum_tree::Cursor<'a, Chunk, usize, usize>,
range: Range<usize>,
}
impl<'a> ChunksIter<'a> {
impl<'a> Chunks<'a> {
pub fn new(rope: &'a Rope, range: Range<usize>) -> Self {
let mut chunks = rope.chunks.cursor();
chunks.seek(&range.start, SeekBias::Right, &());
@@ -284,11 +284,13 @@ impl<'a> ChunksIter<'a> {
self.range.start.max(*self.chunks.start())
}
pub fn advance_to(&mut self, offset: usize) {
pub fn seek(&mut self, offset: usize) {
if offset >= self.chunks.end() {
self.chunks.seek_forward(&offset, SeekBias::Right, &());
self.range.start = offset;
} else {
self.chunks.seek(&offset, SeekBias::Right, &());
}
self.range.start = offset;
}
pub fn peek(&self) -> Option<&'a str> {
@@ -304,7 +306,7 @@ impl<'a> ChunksIter<'a> {
}
}
impl<'a> Iterator for ChunksIter<'a> {
impl<'a> Iterator for Chunks<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
+12 -2
View File
@@ -1,6 +1,6 @@
use crate::{
editor::{
buffer::{Anchor, Buffer, Point, ToPoint},
buffer::{Anchor, Buffer, Point, ToOffset as _, ToPoint as _},
display_map::DisplayMap,
Bias, DisplayPoint,
},
@@ -61,7 +61,7 @@ impl Selection {
}
}
pub fn range(&self, buffer: &Buffer) -> Range<Point> {
pub fn point_range(&self, buffer: &Buffer) -> Range<Point> {
let start = self.start.to_point(buffer);
let end = self.end.to_point(buffer);
if self.reversed {
@@ -71,6 +71,16 @@ impl Selection {
}
}
pub fn offset_range(&self, buffer: &Buffer) -> Range<usize> {
let start = self.start.to_offset(buffer);
let end = self.end.to_offset(buffer);
if self.reversed {
end..start
} else {
start..end
}
}
pub fn display_range(&self, map: &DisplayMap, app: &AppContext) -> Range<DisplayPoint> {
let start = self.start.to_display_point(map, app);
let end = self.end.to_display_point(map, app);
+298 -34
View File
@@ -2,7 +2,12 @@ use super::{
buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point,
Selection, SelectionGoal, SelectionSetId, ToOffset, ToPoint,
};
use crate::{settings::Settings, util::post_inc, workspace, worktree::FileHandle};
use crate::{
settings::{Settings, StyleId},
util::post_inc,
workspace,
worktree::FileHandle,
};
use anyhow::Result;
use gpui::{
color::ColorU, fonts::Properties as FontProperties, geometry::vector::Vector2F,
@@ -161,6 +166,21 @@ pub fn init(app: &mut MutableAppContext) {
"buffer:add_selection_below",
Some("BufferView"),
),
Binding::new(
"alt-up",
"buffer:select_larger_syntax_node",
Some("BufferView"),
),
Binding::new(
"alt-down",
"buffer:select_smaller_syntax_node",
Some("BufferView"),
),
Binding::new(
"ctrl-m",
"buffer:move_to_enclosing_bracket",
Some("BufferView"),
),
Binding::new("pageup", "buffer:page_up", Some("BufferView")),
Binding::new("pagedown", "buffer:page_down", Some("BufferView")),
Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")),
@@ -265,6 +285,18 @@ pub fn init(app: &mut MutableAppContext) {
"buffer:add_selection_below",
BufferView::add_selection_below,
);
app.add_action(
"buffer:select_larger_syntax_node",
BufferView::select_larger_syntax_node,
);
app.add_action(
"buffer:select_smaller_syntax_node",
BufferView::select_smaller_syntax_node,
);
app.add_action(
"buffer:move_to_enclosing_bracket",
BufferView::move_to_enclosing_bracket,
);
app.add_action("buffer:page_up", BufferView::page_up);
app.add_action("buffer:page_down", BufferView::page_down);
app.add_action("buffer:fold", BufferView::fold);
@@ -295,6 +327,7 @@ pub struct BufferView {
pending_selection: Option<Selection>,
next_selection_id: usize,
add_selections_state: Option<AddSelectionsState>,
select_larger_syntax_node_stack: Vec<Vec<Selection>>,
scroll_position: Mutex<Vector2F>,
autoscroll_requested: Mutex<bool>,
settings: watch::Receiver<Settings>,
@@ -354,6 +387,7 @@ impl BufferView {
pending_selection: None,
next_selection_id,
add_selections_state: None,
select_larger_syntax_node_stack: Vec::new(),
scroll_position: Mutex::new(Vector2F::zero()),
autoscroll_requested: Mutex::new(false),
settings,
@@ -690,7 +724,7 @@ impl BufferView {
{
let buffer = self.buffer.read(ctx);
for selection in &mut selections {
let range = selection.range(buffer);
let range = selection.point_range(buffer);
if range.start == range.end {
let head = selection
.head()
@@ -717,7 +751,7 @@ impl BufferView {
{
let buffer = self.buffer.read(ctx);
for selection in &mut selections {
let range = selection.range(buffer);
let range = selection.point_range(buffer);
if range.start == range.end {
let head = selection
.head()
@@ -896,7 +930,7 @@ impl BufferView {
let mut contiguous_selections = Vec::new();
while let Some(selection) = selections.next() {
// Accumulate contiguous regions of rows that we want to move.
contiguous_selections.push(selection.range(buffer));
contiguous_selections.push(selection.point_range(buffer));
let (mut buffer_rows, mut display_rows) =
selection.buffer_rows_for_display_rows(false, &self.display_map, app);
while let Some(next_selection) = selections.peek() {
@@ -905,7 +939,7 @@ impl BufferView {
if next_buffer_rows.start <= buffer_rows.end {
buffer_rows.end = next_buffer_rows.end;
display_rows.end = next_display_rows.end;
contiguous_selections.push(next_selection.range(buffer));
contiguous_selections.push(next_selection.point_range(buffer));
selections.next().unwrap();
} else {
break;
@@ -980,7 +1014,7 @@ impl BufferView {
let mut contiguous_selections = Vec::new();
while let Some(selection) = selections.next() {
// Accumulate contiguous regions of rows that we want to move.
contiguous_selections.push(selection.range(buffer));
contiguous_selections.push(selection.point_range(buffer));
let (mut buffer_rows, mut display_rows) =
selection.buffer_rows_for_display_rows(false, &self.display_map, app);
while let Some(next_selection) = selections.peek() {
@@ -989,7 +1023,7 @@ impl BufferView {
if next_buffer_rows.start <= buffer_rows.end {
buffer_rows.end = next_buffer_rows.end;
display_rows.end = next_display_rows.end;
contiguous_selections.push(next_selection.range(buffer));
contiguous_selections.push(next_selection.point_range(buffer));
selections.next().unwrap();
} else {
break;
@@ -1613,7 +1647,7 @@ impl BufferView {
let mut to_unfold = Vec::new();
let mut new_selections = Vec::new();
for selection in self.selections(app) {
let range = selection.range(buffer).sorted();
let range = selection.point_range(buffer).sorted();
if range.start.row != range.end.row {
new_selections.push(Selection {
id: post_inc(&mut self.next_selection_id),
@@ -1654,7 +1688,7 @@ impl BufferView {
self.add_selection(false, ctx);
}
pub fn add_selection(&mut self, above: bool, ctx: &mut ViewContext<Self>) {
fn add_selection(&mut self, above: bool, ctx: &mut ViewContext<Self>) {
use super::RangeExt;
let app = ctx.as_ref();
@@ -1746,6 +1780,77 @@ impl BufferView {
}
}
pub fn select_larger_syntax_node(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
let app = ctx.as_ref();
let buffer = self.buffer.read(app);
let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
let mut selected_larger_node = false;
let old_selections = self.selections(app).to_vec();
let mut new_selections = Vec::new();
for selection in &old_selections {
let old_range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
let mut new_range = old_range.clone();
while let Some(containing_range) = buffer.range_for_syntax_ancestor(new_range.clone()) {
new_range = containing_range;
if !self.display_map.intersects_fold(new_range.start, app)
&& !self.display_map.intersects_fold(new_range.end, app)
{
break;
}
}
selected_larger_node |= new_range != old_range;
new_selections.push(Selection {
id: selection.id,
start: buffer.anchor_before(new_range.start),
end: buffer.anchor_before(new_range.end),
reversed: selection.reversed,
goal: SelectionGoal::None,
});
}
if selected_larger_node {
stack.push(old_selections);
self.update_selections(new_selections, true, ctx);
}
self.select_larger_syntax_node_stack = stack;
}
pub fn select_smaller_syntax_node(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
let mut stack = mem::take(&mut self.select_larger_syntax_node_stack);
if let Some(selections) = stack.pop() {
self.update_selections(selections, true, ctx);
}
self.select_larger_syntax_node_stack = stack;
}
pub fn move_to_enclosing_bracket(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
use super::RangeExt as _;
let buffer = self.buffer.read(ctx.as_ref());
let mut selections = self.selections(ctx.as_ref()).to_vec();
for selection in &mut selections {
let selection_range = selection.offset_range(buffer);
if let Some((open_range, close_range)) =
buffer.enclosing_bracket_ranges(selection_range.clone())
{
let close_range = close_range.to_inclusive();
let destination = if close_range.contains(&selection_range.start)
&& close_range.contains(&selection_range.end)
{
open_range.end
} else {
*close_range.start()
};
selection.start = buffer.anchor_before(destination);
selection.end = selection.start.clone();
}
}
self.update_selections(selections, true, ctx);
}
fn build_columnar_selection(
&mut self,
row: u32,
@@ -1860,6 +1965,7 @@ impl BufferView {
}
self.add_selections_state = None;
self.select_larger_syntax_node_stack.clear();
}
fn start_transaction(&self, ctx: &mut ViewContext<Self>) {
@@ -1987,7 +2093,7 @@ impl BufferView {
let ranges = self
.selections(ctx.as_ref())
.iter()
.map(|s| s.range(buffer).sorted())
.map(|s| s.point_range(buffer).sorted())
.collect();
self.fold_ranges(ranges, ctx);
}
@@ -2069,10 +2175,7 @@ impl BufferView {
let font_size = settings.buffer_font_size;
let font_id =
font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?;
let digit_count = ((self.buffer.read(app).max_point().row + 1) as f32)
.log10()
.floor() as usize
+ 1;
let digit_count = (self.buffer.read(app).row_count() as f32).log10().floor() as usize + 1;
Ok(layout_cache
.layout_str(
@@ -2135,32 +2238,47 @@ impl BufferView {
}
let settings = self.settings.borrow();
let font_id =
font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?;
let font_size = settings.buffer_font_size;
let font_family = settings.buffer_font_family;
let mut prev_font_properties = FontProperties::new();
let mut prev_font_id = font_cache
.select_font(font_family, &prev_font_properties)
.unwrap();
let mut layouts = Vec::with_capacity(rows.len());
let mut line = String::new();
let mut styles = Vec::new();
let mut row = rows.start;
let snapshot = self.display_map.snapshot(ctx);
let chunks = snapshot.chunks_at(DisplayPoint::new(rows.start, 0), ctx);
for (chunk_row, chunk_line) in chunks
.chain(Some("\n"))
.flat_map(|chunk| chunk.split("\n").enumerate())
{
if chunk_row > 0 {
layouts.push(layout_cache.layout_str(
&line,
font_size,
&[(line.len(), font_id, ColorU::black())],
));
line.clear();
row += 1;
if row == rows.end {
break;
let mut snapshot = self.display_map.snapshot(ctx);
let chunks = snapshot.highlighted_chunks_for_rows(rows.clone());
let theme = settings.theme.clone();
'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", StyleId::default()))) {
for (ix, line_chunk) in chunk.split('\n').enumerate() {
if ix > 0 {
layouts.push(layout_cache.layout_str(&line, font_size, &styles));
line.clear();
styles.clear();
row += 1;
if row == rows.end {
break 'outer;
}
}
if !line_chunk.is_empty() {
let (color, font_properties) = theme.syntax_style(style_ix);
// Avoid a lookup if the font properties match the previous ones.
let font_id = if font_properties == prev_font_properties {
prev_font_id
} else {
font_cache.select_font(font_family, &font_properties)?
};
line.push_str(line_chunk);
styles.push((line_chunk.len(), font_id, color));
prev_font_id = font_id;
prev_font_properties = font_properties;
}
}
line.push_str(chunk_line);
}
Ok(layouts)
@@ -2246,6 +2364,7 @@ impl BufferView {
buffer::Event::Saved => ctx.emit(Event::Saved),
buffer::Event::FileHandleChanged => ctx.emit(Event::FileHandleChanged),
buffer::Event::Reloaded => ctx.emit(Event::FileHandleChanged),
buffer::Event::Reparsed => {}
}
}
}
@@ -2359,7 +2478,12 @@ impl workspace::ItemView for BufferView {
#[cfg(test)]
mod tests {
use super::*;
use crate::{editor::Point, settings, test::sample_text};
use crate::{
editor::Point,
settings,
test::{build_app_state, sample_text},
};
use buffer::History;
use unindent::Unindent;
#[gpui::test]
@@ -3840,6 +3964,146 @@ mod tests {
);
}
#[gpui::test]
async fn test_select_larger_smaller_syntax_node(mut app: gpui::TestAppContext) {
let app_state = app.read(build_app_state);
let lang = app_state.language_registry.select_language("z.rs");
let text = r#"
use mod1::mod2::{mod3, mod4};
fn fn_1(param1: bool, param2: &str) {
let var1 = "text";
}
"#
.unindent();
let buffer = app.add_model(|ctx| {
let history = History::new(text.into());
Buffer::from_history(0, history, None, lang.cloned(), ctx)
});
let (_, view) =
app.add_window(|ctx| BufferView::for_buffer(buffer, app_state.settings, ctx));
view.condition(&app, |view, ctx| !view.buffer.read(ctx).is_parsing())
.await;
view.update(&mut app, |view, ctx| {
view.select_display_ranges(
&[
DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
],
ctx,
)
.unwrap();
view.select_larger_syntax_node(&(), ctx);
});
assert_eq!(
view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
&[
DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
]
);
view.update(&mut app, |view, ctx| {
view.select_larger_syntax_node(&(), ctx);
});
assert_eq!(
view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
&[
DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
]
);
view.update(&mut app, |view, ctx| {
view.select_larger_syntax_node(&(), ctx);
});
assert_eq!(
view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
&[DisplayPoint::new(0, 0)..DisplayPoint::new(5, 0)]
);
// Trying to expand the selected syntax node one more time has no effect.
view.update(&mut app, |view, ctx| {
view.select_larger_syntax_node(&(), ctx);
});
assert_eq!(
view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
&[DisplayPoint::new(0, 0)..DisplayPoint::new(5, 0)]
);
view.update(&mut app, |view, ctx| {
view.select_smaller_syntax_node(&(), ctx);
});
assert_eq!(
view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
&[
DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
]
);
view.update(&mut app, |view, ctx| {
view.select_smaller_syntax_node(&(), ctx);
});
assert_eq!(
view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
&[
DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
]
);
view.update(&mut app, |view, ctx| {
view.select_smaller_syntax_node(&(), ctx);
});
assert_eq!(
view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
&[
DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
]
);
// Trying to shrink the selected syntax node one more time has no effect.
view.update(&mut app, |view, ctx| {
view.select_smaller_syntax_node(&(), ctx);
});
assert_eq!(
view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
&[
DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
]
);
// Ensure that we keep expanding the selection if the larger selection starts or ends within
// a fold.
view.update(&mut app, |view, ctx| {
view.fold_ranges(
vec![
Point::new(0, 21)..Point::new(0, 24),
Point::new(3, 20)..Point::new(3, 22),
],
ctx,
);
view.select_larger_syntax_node(&(), ctx);
});
assert_eq!(
view.read_with(&app, |view, ctx| view.selection_ranges(ctx)),
&[
DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
]
);
}
impl BufferView {
fn selection_ranges(&self, app: &AppContext) -> Vec<Range<DisplayPoint>> {
self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app)
+128 -37
View File
@@ -4,6 +4,7 @@ use super::{
};
use crate::{
editor::buffer,
settings::StyleId,
sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
time,
};
@@ -45,7 +46,7 @@ impl FoldMap {
pub fn snapshot(&self, ctx: &AppContext) -> FoldMapSnapshot {
FoldMapSnapshot {
transforms: self.sync(ctx).clone(),
buffer: self.buffer.clone(),
buffer: self.buffer.read(ctx).snapshot(),
}
}
@@ -100,6 +101,7 @@ impl FoldMap {
edits.push(Edit {
old_range: range.clone(),
new_range: range.clone(),
..Default::default()
});
}
}
@@ -144,6 +146,7 @@ impl FoldMap {
edits.push(Edit {
old_range: offset_range.clone(),
new_range: offset_range,
..Default::default()
});
fold_ixs_to_delete.push(*folds_cursor.start());
folds_cursor.next();
@@ -189,6 +192,18 @@ impl FoldMap {
})
}
pub fn intersects_fold<T>(&self, offset: T, ctx: &AppContext) -> bool
where
T: ToOffset,
{
let buffer = self.buffer.read(ctx);
let offset = offset.to_offset(buffer);
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<usize, usize>();
cursor.seek(&offset, SeekBias::Right, &());
cursor.item().map_or(false, |t| t.display_text.is_some())
}
pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<DisplayPoint, DisplayPoint>();
@@ -207,11 +222,11 @@ impl FoldMap {
}
pub fn to_buffer_offset(&self, point: DisplayPoint, ctx: &AppContext) -> usize {
self.snapshot(ctx).to_buffer_offset(point, ctx)
self.snapshot(ctx).to_buffer_offset(point)
}
pub fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> DisplayOffset {
self.snapshot(ctx).to_display_offset(point, ctx)
self.snapshot(ctx).to_display_offset(point)
}
pub fn to_buffer_point(&self, display_point: DisplayPoint, ctx: &AppContext) -> Point {
@@ -391,7 +406,7 @@ impl FoldMap {
pub struct FoldMapSnapshot {
transforms: SumTree<Transform>,
buffer: ModelHandle<Buffer>,
buffer: buffer::Snapshot,
}
impl FoldMapSnapshot {
@@ -410,30 +425,49 @@ impl FoldMapSnapshot {
}
}
pub fn chunks_at<'a>(&'a self, offset: DisplayOffset, ctx: &'a AppContext) -> Chunks<'a> {
pub fn max_point(&self) -> DisplayPoint {
DisplayPoint(self.transforms.summary().display.lines)
}
pub fn chunks_at(&self, offset: DisplayOffset) -> Chunks {
let mut transform_cursor = self.transforms.cursor::<DisplayOffset, TransformSummary>();
transform_cursor.seek(&offset, SeekBias::Right, &());
let overshoot = offset.0 - transform_cursor.start().display.bytes;
let buffer_offset = transform_cursor.start().buffer.bytes + overshoot;
let buffer = self.buffer.read(ctx);
let rope_cursor = buffer.text_for_range(buffer_offset..buffer.len());
Chunks {
transform_cursor,
buffer_offset,
buffer_chunks: rope_cursor,
buffer_chunks: self.buffer.text_for_range(buffer_offset..self.buffer.len()),
}
}
pub fn chars_at<'a>(
&'a self,
point: DisplayPoint,
ctx: &'a AppContext,
) -> impl Iterator<Item = char> + 'a {
let offset = self.to_display_offset(point, ctx);
self.chunks_at(offset, ctx).flat_map(str::chars)
pub fn highlighted_chunks(&mut self, range: Range<DisplayOffset>) -> HighlightedChunks {
let mut transform_cursor = self.transforms.cursor::<DisplayOffset, TransformSummary>();
transform_cursor.seek(&range.end, SeekBias::Right, &());
let overshoot = range.end.0 - transform_cursor.start().display.bytes;
let buffer_end = transform_cursor.start().buffer.bytes + overshoot;
transform_cursor.seek(&range.start, SeekBias::Right, &());
let overshoot = range.start.0 - transform_cursor.start().display.bytes;
let buffer_start = transform_cursor.start().buffer.bytes + overshoot;
HighlightedChunks {
transform_cursor,
buffer_offset: buffer_start,
buffer_chunks: self
.buffer
.highlighted_text_for_range(buffer_start..buffer_end),
buffer_chunk: None,
}
}
pub fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> DisplayOffset {
pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
let offset = self.to_display_offset(point);
self.chunks_at(offset).flat_map(str::chars)
}
pub fn to_display_offset(&self, point: DisplayPoint) -> DisplayOffset {
let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
cursor.seek(&point, SeekBias::Right, &());
let overshoot = point.0 - cursor.start().display.lines;
@@ -441,27 +475,24 @@ impl FoldMapSnapshot {
if !overshoot.is_zero() {
let transform = cursor.item().expect("display point out of range");
assert!(transform.display_text.is_none());
let end_buffer_offset =
(cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx));
let end_buffer_offset = self
.buffer
.to_offset(cursor.start().buffer.lines + overshoot);
offset += end_buffer_offset - cursor.start().buffer.bytes;
}
DisplayOffset(offset)
}
pub fn to_buffer_offset(&self, point: DisplayPoint, ctx: &AppContext) -> usize {
pub fn to_buffer_offset(&self, point: DisplayPoint) -> usize {
let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
cursor.seek(&point, SeekBias::Right, &());
let overshoot = point.0 - cursor.start().display.lines;
(cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx))
self.buffer
.to_offset(cursor.start().buffer.lines + overshoot)
}
#[cfg(test)]
pub fn clip_offset(
&self,
offset: DisplayOffset,
bias: Bias,
ctx: &AppContext,
) -> DisplayOffset {
pub fn clip_offset(&self, offset: DisplayOffset, bias: Bias) -> DisplayOffset {
let mut cursor = self.transforms.cursor::<DisplayOffset, TransformSummary>();
cursor.seek(&offset, SeekBias::Right, &());
if let Some(transform) = cursor.item() {
@@ -475,7 +506,7 @@ impl FoldMapSnapshot {
} else {
let overshoot = offset.0 - transform_start;
let buffer_offset = cursor.start().buffer.bytes + overshoot;
let clipped_buffer_offset = self.buffer.read(ctx).clip_offset(buffer_offset, bias);
let clipped_buffer_offset = self.buffer.clip_offset(buffer_offset, bias);
DisplayOffset(
(offset.0 as isize + (clipped_buffer_offset as isize - buffer_offset as isize))
as usize,
@@ -486,7 +517,7 @@ impl FoldMapSnapshot {
}
}
pub fn clip_point(&self, point: DisplayPoint, bias: Bias, ctx: &AppContext) -> DisplayPoint {
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
cursor.seek(&point, SeekBias::Right, &());
if let Some(transform) = cursor.item() {
@@ -500,8 +531,7 @@ impl FoldMapSnapshot {
} else {
let overshoot = point.0 - transform_start;
let buffer_position = cursor.start().buffer.lines + overshoot;
let clipped_buffer_position =
self.buffer.read(ctx).clip_point(buffer_position, bias);
let clipped_buffer_position = self.buffer.clip_point(buffer_position, bias);
DisplayPoint::new(
point.row(),
((point.column() as i32) + clipped_buffer_position.column as i32
@@ -667,7 +697,7 @@ impl<'a> Iterator for BufferRows<'a> {
pub struct Chunks<'a> {
transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
buffer_chunks: buffer::ChunksIter<'a>,
buffer_chunks: buffer::Chunks<'a>,
buffer_offset: usize,
}
@@ -685,7 +715,7 @@ impl<'a> Iterator for Chunks<'a> {
// advance the transform and buffer cursors to the end of the fold.
if let Some(display_text) = transform.display_text {
self.buffer_offset += transform.summary.buffer.bytes;
self.buffer_chunks.advance_to(self.buffer_offset);
self.buffer_chunks.seek(self.buffer_offset);
while self.buffer_offset >= self.transform_cursor.end().buffer.bytes
&& self.transform_cursor.item().is_some()
@@ -718,6 +748,70 @@ impl<'a> Iterator for Chunks<'a> {
}
}
pub struct HighlightedChunks<'a> {
transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
buffer_chunks: buffer::HighlightedChunks<'a>,
buffer_chunk: Option<(usize, &'a str, StyleId)>,
buffer_offset: usize,
}
impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, StyleId);
fn next(&mut self) -> Option<Self::Item> {
let transform = if let Some(item) = self.transform_cursor.item() {
item
} else {
return None;
};
// If we're in a fold, then return the fold's display text and
// advance the transform and buffer cursors to the end of the fold.
if let Some(display_text) = transform.display_text {
self.buffer_chunk.take();
self.buffer_offset += transform.summary.buffer.bytes;
self.buffer_chunks.seek(self.buffer_offset);
while self.buffer_offset >= self.transform_cursor.end().buffer.bytes
&& self.transform_cursor.item().is_some()
{
self.transform_cursor.next();
}
return Some((display_text, StyleId::default()));
}
// Retrieve a chunk from the current location in the buffer.
if self.buffer_chunk.is_none() {
let chunk_offset = self.buffer_chunks.offset();
self.buffer_chunk = self
.buffer_chunks
.next()
.map(|(chunk, capture_ix)| (chunk_offset, chunk, capture_ix));
}
// Otherwise, take a chunk from the buffer's text.
if let Some((chunk_offset, mut chunk, capture_ix)) = self.buffer_chunk {
let offset_in_chunk = self.buffer_offset - chunk_offset;
chunk = &chunk[offset_in_chunk..];
// Truncate the chunk so that it ends at the next fold.
let region_end = self.transform_cursor.end().buffer.bytes - self.buffer_offset;
if chunk.len() >= region_end {
chunk = &chunk[0..region_end];
self.transform_cursor.next();
} else {
self.buffer_chunk.take();
}
self.buffer_offset += chunk.len();
return Some((chunk, capture_ix));
}
None
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for DisplayPoint {
fn add_summary(&mut self, summary: &'a TransformSummary) {
self.0 += &summary.display.lines;
@@ -1046,11 +1140,10 @@ mod tests {
let offset = map.snapshot(app.as_ref()).clip_offset(
DisplayOffset(rng.gen_range(0..=map.len(app.as_ref()))),
Bias::Right,
app.as_ref(),
);
assert_eq!(
map.snapshot(app.as_ref())
.chunks_at(offset, app.as_ref())
.chunks_at(offset)
.collect::<String>(),
&expected_text[offset.0..],
);
@@ -1133,9 +1226,7 @@ mod tests {
impl FoldMap {
fn text(&self, app: &AppContext) -> String {
self.snapshot(app)
.chunks_at(DisplayOffset(0), app)
.collect()
self.snapshot(app).chunks_at(DisplayOffset(0)).collect()
}
fn merged_fold_ranges(&self, app: &AppContext) -> Vec<Range<usize>> {
+209 -54
View File
@@ -1,10 +1,12 @@
mod fold_map;
use crate::settings::StyleId;
use super::{buffer, Anchor, Bias, Buffer, Edit, Point, ToOffset, ToPoint};
pub use fold_map::BufferRows;
use fold_map::{FoldMap, FoldMapSnapshot};
use gpui::{AppContext, ModelHandle};
use std::ops::Range;
use std::{mem, ops::Range};
pub struct DisplayMap {
buffer: ModelHandle<Buffer>,
@@ -55,21 +57,23 @@ impl DisplayMap {
self.fold_map.unfold(ranges, ctx)
}
pub fn intersects_fold<T: ToOffset>(&self, offset: T, ctx: &AppContext) -> bool {
self.fold_map.intersects_fold(offset, ctx)
}
pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool {
self.fold_map.is_line_folded(display_row, ctx)
}
pub fn text(&self, ctx: &AppContext) -> String {
self.snapshot(ctx)
.chunks_at(DisplayPoint::zero(), ctx)
.collect()
self.snapshot(ctx).chunks_at(DisplayPoint::zero()).collect()
}
pub fn line(&self, display_row: u32, ctx: &AppContext) -> String {
let mut result = String::new();
for chunk in self
.snapshot(ctx)
.chunks_at(DisplayPoint::new(display_row, 0), ctx)
.chunks_at(DisplayPoint::new(display_row, 0))
{
if let Some(ix) = chunk.find('\n') {
result.push_str(&chunk[0..ix]);
@@ -86,7 +90,7 @@ impl DisplayMap {
let mut is_blank = true;
for c in self
.snapshot(ctx)
.chars_at(DisplayPoint::new(display_row, 0), ctx)
.chars_at(DisplayPoint::new(display_row, 0))
{
if c == ' ' {
indent += 1;
@@ -104,9 +108,8 @@ impl DisplayMap {
.column()
}
// TODO - make this delegate to the DisplayMapSnapshot
pub fn max_point(&self, ctx: &AppContext) -> DisplayPoint {
self.fold_map.max_point(ctx).expand_tabs(self, ctx)
self.snapshot(ctx).max_point().expand_tabs(self, ctx)
}
pub fn longest_row(&self, ctx: &AppContext) -> u32 {
@@ -136,12 +139,15 @@ impl DisplayMapSnapshot {
self.folds_snapshot.buffer_rows(start_row)
}
pub fn chunks_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Chunks<'a> {
let (point, expanded_char_column, to_next_stop) =
self.collapse_tabs(point, Bias::Left, app);
pub fn max_point(&self) -> DisplayPoint {
self.expand_tabs(self.folds_snapshot.max_point())
}
pub fn chunks_at(&self, point: DisplayPoint) -> Chunks {
let (point, expanded_char_column, to_next_stop) = self.collapse_tabs(point, Bias::Left);
let fold_chunks = self
.folds_snapshot
.chunks_at(self.folds_snapshot.to_display_offset(point, app), app);
.chunks_at(self.folds_snapshot.to_display_offset(point));
Chunks {
fold_chunks,
column: expanded_char_column,
@@ -151,18 +157,28 @@ impl DisplayMapSnapshot {
}
}
pub fn chars_at<'a>(
&'a self,
point: DisplayPoint,
app: &'a AppContext,
) -> impl Iterator<Item = char> + 'a {
self.chunks_at(point, app).flat_map(str::chars)
pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
let start = DisplayPoint::new(rows.start, 0);
let start = self.folds_snapshot.to_display_offset(start);
let end = DisplayPoint::new(rows.end, 0).min(self.max_point());
let end = self.folds_snapshot.to_display_offset(end);
HighlightedChunks {
fold_chunks: self.folds_snapshot.highlighted_chunks(start..end),
column: 0,
tab_size: self.tab_size,
chunk: "",
style_id: Default::default(),
}
}
pub fn column_to_chars(&self, display_row: u32, target: u32, ctx: &AppContext) -> u32 {
pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
self.chunks_at(point).flat_map(str::chars)
}
pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
let mut count = 0;
let mut column = 0;
for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) {
for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
if column >= target {
break;
}
@@ -172,10 +188,10 @@ impl DisplayMapSnapshot {
count
}
pub fn column_from_chars(&self, display_row: u32, char_count: u32, ctx: &AppContext) -> u32 {
pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
let mut count = 0;
let mut column = 0;
for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) {
for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
if c == '\n' || count >= char_count {
break;
}
@@ -185,32 +201,26 @@ impl DisplayMapSnapshot {
column
}
pub fn clip_point(&self, point: DisplayPoint, bias: Bias, ctx: &AppContext) -> DisplayPoint {
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
self.expand_tabs(
self.folds_snapshot
.clip_point(self.collapse_tabs(point, bias, ctx).0, bias, ctx),
ctx,
.clip_point(self.collapse_tabs(point, bias).0, bias),
)
}
fn expand_tabs(&self, mut point: DisplayPoint, ctx: &AppContext) -> DisplayPoint {
fn expand_tabs(&self, mut point: DisplayPoint) -> DisplayPoint {
let chars = self
.folds_snapshot
.chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx);
.chars_at(DisplayPoint(Point::new(point.row(), 0)));
let expanded = expand_tabs(chars, point.column() as usize, self.tab_size);
*point.column_mut() = expanded as u32;
point
}
fn collapse_tabs(
&self,
mut point: DisplayPoint,
bias: Bias,
ctx: &AppContext,
) -> (DisplayPoint, usize, usize) {
fn collapse_tabs(&self, mut point: DisplayPoint, bias: Bias) -> (DisplayPoint, usize, usize) {
let chars = self
.folds_snapshot
.chars_at(DisplayPoint(Point::new(point.row(), 0)), ctx);
.chars_at(DisplayPoint(Point::new(point.row(), 0)));
let expanded = point.column() as usize;
let (collapsed, expanded_char_column, to_next_stop) =
collapse_tabs(chars, expanded, bias, self.tab_size);
@@ -258,11 +268,11 @@ impl DisplayPoint {
}
fn expand_tabs(self, map: &DisplayMap, ctx: &AppContext) -> Self {
map.snapshot(ctx).expand_tabs(self, ctx)
map.snapshot(ctx).expand_tabs(self)
}
fn collapse_tabs(self, map: &DisplayMap, bias: Bias, ctx: &AppContext) -> Self {
map.snapshot(ctx).collapse_tabs(self, bias, ctx).0
map.snapshot(ctx).collapse_tabs(self, bias).0
}
}
@@ -270,7 +280,7 @@ impl Point {
pub fn to_display_point(self, map: &DisplayMap, ctx: &AppContext) -> DisplayPoint {
let mut display_point = map.fold_map.to_display_point(self, ctx);
let snapshot = map.fold_map.snapshot(ctx);
let chars = snapshot.chars_at(DisplayPoint::new(display_point.row(), 0), ctx);
let chars = snapshot.chars_at(DisplayPoint::new(display_point.row(), 0));
*display_point.column_mut() =
expand_tabs(chars, display_point.column() as usize, map.tab_size) as u32;
display_point
@@ -336,6 +346,50 @@ impl<'a> Iterator for Chunks<'a> {
}
}
pub struct HighlightedChunks<'a> {
fold_chunks: fold_map::HighlightedChunks<'a>,
chunk: &'a str,
style_id: StyleId,
column: usize,
tab_size: usize,
}
impl<'a> Iterator for HighlightedChunks<'a> {
type Item = (&'a str, StyleId);
fn next(&mut self) -> Option<Self::Item> {
if self.chunk.is_empty() {
if let Some((chunk, style_id)) = self.fold_chunks.next() {
self.chunk = chunk;
self.style_id = style_id;
} else {
return None;
}
}
for (ix, c) in self.chunk.char_indices() {
match c {
'\t' => {
if ix > 0 {
let (prefix, suffix) = self.chunk.split_at(ix);
self.chunk = suffix;
return Some((prefix, self.style_id));
} else {
self.chunk = &self.chunk[1..];
let len = self.tab_size - self.column % self.tab_size;
self.column += len;
return Some((&SPACES[0..len], self.style_id));
}
}
'\n' => self.column = 0,
_ => self.column += 1,
}
}
Some((mem::take(&mut self.chunk), mem::take(&mut self.style_id)))
}
}
pub fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
let mut expanded_chars = 0;
let mut expanded_bytes = 0;
@@ -400,7 +454,13 @@ pub fn collapse_tabs(
#[cfg(test)]
mod tests {
use super::*;
use crate::test::*;
use crate::{
language::{Language, LanguageConfig},
settings::Theme,
test::*,
};
use buffer::History;
use std::sync::Arc;
#[gpui::test]
fn test_chunks_at(app: &mut gpui::MutableAppContext) {
@@ -423,24 +483,125 @@ mod tests {
assert_eq!(
&map.snapshot(app.as_ref())
.chunks_at(DisplayPoint::new(1, 0), app.as_ref())
.chunks_at(DisplayPoint::new(1, 0))
.collect::<String>()[0..10],
" b bb"
);
assert_eq!(
&map.snapshot(app.as_ref())
.chunks_at(DisplayPoint::new(1, 2), app.as_ref())
.chunks_at(DisplayPoint::new(1, 2))
.collect::<String>()[0..10],
" b bbbb"
);
assert_eq!(
&map.snapshot(app.as_ref())
.chunks_at(DisplayPoint::new(1, 6), app.as_ref())
.chunks_at(DisplayPoint::new(1, 6))
.collect::<String>()[0..13],
" bbbbb\nc c"
);
}
#[gpui::test]
async fn test_highlighted_chunks_at(mut app: gpui::TestAppContext) {
use unindent::Unindent as _;
let grammar = tree_sitter_rust::language();
let text = r#"
fn outer() {}
mod module {
fn inner() {}
}"#
.unindent();
let highlight_query = tree_sitter::Query::new(
grammar,
r#"
(mod_item name: (identifier) body: _ @mod.body)
(function_item name: (identifier) @fn.name)"#,
)
.unwrap();
let theme = Theme::parse(
r#"
[syntax]
"mod.body" = 0xff0000
"fn.name" = 0x00ff00"#,
)
.unwrap();
let lang = Arc::new(Language {
config: LanguageConfig {
name: "Test".to_string(),
path_suffixes: vec![".test".to_string()],
..Default::default()
},
grammar: grammar.clone(),
highlight_query,
brackets_query: tree_sitter::Query::new(grammar, "").unwrap(),
theme_mapping: Default::default(),
});
lang.set_theme(&theme);
let buffer = app.add_model(|ctx| {
Buffer::from_history(0, History::new(text.into()), None, Some(lang), ctx)
});
buffer.condition(&app, |buf, _| !buf.is_parsing()).await;
let mut map = app.read(|ctx| DisplayMap::new(buffer, 2, ctx));
assert_eq!(
app.read(|ctx| highlighted_chunks(0..5, &map, &theme, ctx)),
vec![
("fn ".to_string(), None),
("outer".to_string(), Some("fn.name")),
("() {}\n\nmod module ".to_string(), None),
("{\n fn ".to_string(), Some("mod.body")),
("inner".to_string(), Some("fn.name")),
("() {}\n}".to_string(), Some("mod.body")),
]
);
assert_eq!(
app.read(|ctx| highlighted_chunks(3..5, &map, &theme, ctx)),
vec![
(" fn ".to_string(), Some("mod.body")),
("inner".to_string(), Some("fn.name")),
("() {}\n}".to_string(), Some("mod.body")),
]
);
app.read(|ctx| map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], ctx));
assert_eq!(
app.read(|ctx| highlighted_chunks(0..2, &map, &theme, ctx)),
vec![
("fn ".to_string(), None),
("out".to_string(), Some("fn.name")),
("".to_string(), None),
(" fn ".to_string(), Some("mod.body")),
("inner".to_string(), Some("fn.name")),
("() {}\n}".to_string(), Some("mod.body")),
]
);
fn highlighted_chunks<'a>(
rows: Range<u32>,
map: &DisplayMap,
theme: &'a Theme,
ctx: &AppContext,
) -> Vec<(String, Option<&'a str>)> {
let mut chunks: Vec<(String, Option<&str>)> = Vec::new();
for (chunk, style_id) in map.snapshot(ctx).highlighted_chunks_for_rows(rows) {
let style_name = theme.syntax_style_name(style_id);
if let Some((last_chunk, last_style_name)) = chunks.last_mut() {
if style_name == *last_style_name {
last_chunk.push_str(chunk);
} else {
chunks.push((chunk.to_string(), style_name));
}
} else {
chunks.push((chunk.to_string(), style_name));
}
}
chunks
}
}
#[gpui::test]
fn test_clip_point(app: &mut gpui::MutableAppContext) {
use Bias::{Left, Right};
@@ -470,7 +631,7 @@ mod tests {
),
] {
assert_eq!(
map.clip_point(DisplayPoint::new(1, input_column as u32), bias, ctx),
map.clip_point(DisplayPoint::new(1, input_column as u32), bias),
DisplayPoint::new(1, output_column as u32),
"clip_point(({}, {}))",
1,
@@ -520,7 +681,7 @@ mod tests {
);
assert_eq!(
map.snapshot(ctx)
.chunks_at(DisplayPoint::new(0, "".len() as u32), ctx)
.chunks_at(DisplayPoint::new(0, "".len() as u32))
.collect::<String>(),
" α\nβ \n🏀β γ"
);
@@ -534,26 +695,20 @@ mod tests {
);
assert_eq!(
map.snapshot(ctx)
.chunks_at(DisplayPoint::new(0, "".len() as u32), ctx)
.chunks_at(DisplayPoint::new(0, "".len() as u32))
.collect::<String>(),
" α\nβ \n🏀β γ"
);
// Clipping display points inside of multi-byte characters
assert_eq!(
map.snapshot(ctx).clip_point(
DisplayPoint::new(0, "".len() as u32 - 1),
Bias::Left,
ctx
),
map.snapshot(ctx)
.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Left),
DisplayPoint::new(0, 0)
);
assert_eq!(
map.snapshot(ctx).clip_point(
DisplayPoint::new(0, "".len() as u32 - 1),
Bias::Right,
ctx
),
map.snapshot(ctx)
.clip_point(DisplayPoint::new(0, "".len() as u32 - 1), Bias::Right),
DisplayPoint::new(0, "".len() as u32)
);
}
+9 -1
View File
@@ -9,7 +9,10 @@ pub use buffer_element::*;
pub use buffer_view::*;
pub use display_map::DisplayPoint;
use display_map::*;
use std::{cmp, ops::Range};
use std::{
cmp,
ops::{Range, RangeInclusive},
};
#[derive(Copy, Clone)]
pub enum Bias {
@@ -19,10 +22,15 @@ pub enum Bias {
trait RangeExt<T> {
fn sorted(&self) -> Range<T>;
fn to_inclusive(&self) -> RangeInclusive<T>;
}
impl<T: Ord + Clone> RangeExt<T> for Range<T> {
fn sorted(&self) -> Self {
cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
}
fn to_inclusive(&self) -> RangeInclusive<T> {
self.start.clone()..=self.end.clone()
}
}
+8 -8
View File
@@ -9,7 +9,7 @@ pub fn left(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Resu
*point.row_mut() -= 1;
*point.column_mut() = map.line_len(point.row(), app);
}
Ok(map.snapshot(app).clip_point(point, Bias::Left, app))
Ok(map.snapshot(app).clip_point(point, Bias::Left))
}
pub fn right(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Result<DisplayPoint> {
@@ -20,7 +20,7 @@ pub fn right(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Res
*point.row_mut() += 1;
*point.column_mut() = 0;
}
Ok(map.snapshot(app).clip_point(point, Bias::Right, app))
Ok(map.snapshot(app).clip_point(point, Bias::Right))
}
pub fn up(
@@ -33,12 +33,12 @@ pub fn up(
let goal_column = if let SelectionGoal::Column(column) = goal {
column
} else {
map.column_to_chars(point.row(), point.column(), app)
map.column_to_chars(point.row(), point.column())
};
if point.row() > 0 {
*point.row_mut() -= 1;
*point.column_mut() = map.column_from_chars(point.row(), goal_column, app);
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
} else {
point = DisplayPoint::new(0, 0);
}
@@ -57,12 +57,12 @@ pub fn down(
let goal_column = if let SelectionGoal::Column(column) = goal {
column
} else {
map.column_to_chars(point.row(), point.column(), app)
map.column_to_chars(point.row(), point.column())
};
if point.row() < max_point.row() {
*point.row_mut() += 1;
*point.column_mut() = map.column_from_chars(point.row(), goal_column, app);
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
} else {
point = max_point;
}
@@ -107,7 +107,7 @@ pub fn prev_word_boundary(
let mut boundary = DisplayPoint::new(point.row(), 0);
let mut column = 0;
let mut prev_c = None;
for c in map.snapshot(app).chars_at(boundary, app) {
for c in map.snapshot(app).chars_at(boundary) {
if column >= point.column() {
break;
}
@@ -129,7 +129,7 @@ pub fn next_word_boundary(
app: &AppContext,
) -> Result<DisplayPoint> {
let mut prev_c = None;
for c in map.snapshot(app).chars_at(point, app) {
for c in map.snapshot(app).chars_at(point) {
if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) {
break;
}
+38 -12
View File
@@ -458,7 +458,11 @@ impl FileFinder {
#[cfg(test)]
mod tests {
use super::*;
use crate::{editor, settings, test::temp_tree, workspace::Workspace};
use crate::{
editor,
test::{build_app_state, temp_tree},
workspace::Workspace,
};
use serde_json::json;
use std::fs;
use tempdir::TempDir;
@@ -474,9 +478,10 @@ mod tests {
editor::init(ctx);
});
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (window_id, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(tmp_dir.path(), ctx);
workspace
});
@@ -541,15 +546,21 @@ mod tests {
"hi": "",
"hiccup": "",
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings.clone(), ctx);
let mut workspace = Workspace::new(
0,
app_state.settings.clone(),
app_state.language_registry.clone(),
ctx,
);
workspace.add_worktree(tmp_dir.path(), ctx);
workspace
});
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await;
let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
let (_, finder) =
app.add_window(|ctx| FileFinder::new(app_state.settings, workspace.clone(), ctx));
let query = "hi".to_string();
finder
@@ -598,15 +609,21 @@ mod tests {
fs::create_dir(&dir_path).unwrap();
fs::write(&file_path, "").unwrap();
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings.clone(), ctx);
let mut workspace = Workspace::new(
0,
app_state.settings.clone(),
app_state.language_registry.clone(),
ctx,
);
workspace.add_worktree(&file_path, ctx);
workspace
});
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await;
let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
let (_, finder) =
app.add_window(|ctx| FileFinder::new(app_state.settings, workspace.clone(), ctx));
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
@@ -641,9 +658,17 @@ mod tests {
"dir1": { "a.txt": "" },
"dir2": { "a.txt": "" }
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, workspace) = app.add_window(|ctx| Workspace::new(0, settings.clone(), ctx));
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
Workspace::new(
0,
app_state.settings.clone(),
app_state.language_registry.clone(),
ctx,
)
});
workspace
.update(&mut app, |workspace, ctx| {
@@ -656,7 +681,8 @@ mod tests {
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await;
let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
let (_, finder) =
app.add_window(|ctx| FileFinder::new(app_state.settings, workspace.clone(), ctx));
// Run a search that matches two files with the same relative path.
finder
+152
View File
@@ -0,0 +1,152 @@
use crate::settings::{Theme, ThemeMap};
use parking_lot::Mutex;
use rust_embed::RustEmbed;
use serde::Deserialize;
use std::{path::Path, str, sync::Arc};
use tree_sitter::{Language as Grammar, Query};
pub use tree_sitter::{Parser, Tree};
#[derive(RustEmbed)]
#[folder = "languages"]
pub struct LanguageDir;
#[derive(Default, Deserialize)]
pub struct LanguageConfig {
pub name: String,
pub path_suffixes: Vec<String>,
}
#[derive(Deserialize)]
pub struct BracketPair {
pub start: String,
pub end: String,
}
pub struct Language {
pub config: LanguageConfig,
pub grammar: Grammar,
pub highlight_query: Query,
pub brackets_query: Query,
pub theme_mapping: Mutex<ThemeMap>,
}
pub struct LanguageRegistry {
languages: Vec<Arc<Language>>,
}
impl Language {
pub fn theme_mapping(&self) -> ThemeMap {
self.theme_mapping.lock().clone()
}
pub fn set_theme(&self, theme: &Theme) {
*self.theme_mapping.lock() = ThemeMap::new(self.highlight_query.capture_names(), theme);
}
}
impl LanguageRegistry {
pub fn new() -> Self {
let grammar = tree_sitter_rust::language();
let rust_config = toml::from_slice(&LanguageDir::get("rust/config.toml").unwrap()).unwrap();
let rust_language = Language {
config: rust_config,
grammar,
highlight_query: Self::load_query(grammar, "rust/highlights.scm"),
brackets_query: Self::load_query(grammar, "rust/brackets.scm"),
theme_mapping: Mutex::new(ThemeMap::default()),
};
Self {
languages: vec![Arc::new(rust_language)],
}
}
pub fn set_theme(&self, theme: &Theme) {
for language in &self.languages {
language.set_theme(theme);
}
}
pub fn select_language(&self, path: impl AsRef<Path>) -> Option<&Arc<Language>> {
let path = path.as_ref();
let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension().and_then(|name| name.to_str());
let path_suffixes = [extension, filename];
self.languages.iter().find(|language| {
language
.config
.path_suffixes
.iter()
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())))
})
}
fn load_query(grammar: tree_sitter::Language, path: &str) -> Query {
Query::new(
grammar,
str::from_utf8(LanguageDir::get(path).unwrap().as_ref()).unwrap(),
)
.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_select_language() {
let grammar = tree_sitter_rust::language();
let registry = LanguageRegistry {
languages: vec![
Arc::new(Language {
config: LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
grammar,
highlight_query: Query::new(grammar, "").unwrap(),
brackets_query: Query::new(grammar, "").unwrap(),
theme_mapping: Default::default(),
}),
Arc::new(Language {
config: LanguageConfig {
name: "Make".to_string(),
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
..Default::default()
},
grammar,
highlight_query: Query::new(grammar, "").unwrap(),
brackets_query: Query::new(grammar, "").unwrap(),
theme_mapping: Default::default(),
}),
],
};
// matching file extension
assert_eq!(
registry.select_language("zed/lib.rs").map(get_name),
Some("Rust")
);
assert_eq!(
registry.select_language("zed/lib.mk").map(get_name),
Some("Make")
);
// matching filename
assert_eq!(
registry.select_language("zed/Makefile").map(get_name),
Some("Make")
);
// matching suffix that is not the full file extension or filename
assert_eq!(registry.select_language("zed/cars").map(get_name), None);
assert_eq!(registry.select_language("zed/a.cars").map(get_name), None);
assert_eq!(registry.select_language("zed/sumk").map(get_name), None);
fn get_name(language: &Arc<Language>) -> &str {
language.config.name.as_str()
}
}
}
+7
View File
@@ -1,6 +1,7 @@
pub mod assets;
pub mod editor;
pub mod file_finder;
pub mod language;
pub mod menus;
mod operation_queue;
pub mod settings;
@@ -11,3 +12,9 @@ mod time;
mod util;
pub mod workspace;
mod worktree;
#[derive(Clone)]
pub struct AppState {
pub settings: postage::watch::Receiver<settings::Settings>,
pub language_registry: std::sync::Arc<language::LanguageRegistry>,
}
+14 -5
View File
@@ -4,19 +4,28 @@
use fs::OpenOptions;
use log::LevelFilter;
use simplelog::SimpleLogger;
use std::{fs, path::PathBuf};
use std::{fs, path::PathBuf, sync::Arc};
use zed::{
assets, editor, file_finder, menus, settings,
assets, editor, file_finder, language, menus, settings,
workspace::{self, OpenParams},
AppState,
};
fn main() {
init_logger();
let app = gpui::App::new(assets::Assets).unwrap();
let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap();
let (_, settings) = settings::channel(&app.font_cache()).unwrap();
let language_registry = Arc::new(language::LanguageRegistry::new());
language_registry.set_theme(&settings.borrow().theme);
let app_state = AppState {
language_registry,
settings,
};
app.run(move |ctx| {
ctx.set_menus(menus::menus(settings_rx.clone()));
ctx.set_menus(menus::menus(app_state.settings.clone()));
workspace::init(ctx);
editor::init(ctx);
file_finder::init(ctx);
@@ -31,7 +40,7 @@ fn main() {
"workspace:open_paths",
OpenParams {
paths,
settings: settings_rx,
app_state: app_state.clone(),
},
);
}
+273 -2
View File
@@ -1,6 +1,15 @@
use anyhow::Result;
use gpui::font_cache::{FamilyId, FontCache};
use super::assets::Assets;
use anyhow::{anyhow, Context, Result};
use gpui::{
color::ColorU,
font_cache::{FamilyId, FontCache},
fonts::{Properties as FontProperties, Style as FontStyle, Weight as FontWeight},
};
use postage::watch;
use serde::Deserialize;
use std::{collections::HashMap, sync::Arc};
const DEFAULT_STYLE_ID: StyleId = StyleId(u32::MAX);
#[derive(Clone)]
pub struct Settings {
@@ -9,8 +18,23 @@ pub struct Settings {
pub tab_size: usize,
pub ui_font_family: FamilyId,
pub ui_font_size: f32,
pub theme: Arc<Theme>,
}
#[derive(Clone, Default)]
pub struct Theme {
pub background_color: ColorU,
pub line_number_color: ColorU,
pub default_text_color: ColorU,
syntax_styles: Vec<(String, ColorU, FontProperties)>,
}
#[derive(Clone, Debug)]
pub struct ThemeMap(Arc<[StyleId]>);
#[derive(Clone, Copy, Debug)]
pub struct StyleId(u32);
impl Settings {
pub fn new(font_cache: &FontCache) -> Result<Self> {
Ok(Self {
@@ -19,12 +43,259 @@ impl Settings {
tab_size: 4,
ui_font_family: font_cache.load_family(&["SF Pro", "Helvetica"])?,
ui_font_size: 12.0,
theme: Arc::new(
Theme::parse(Assets::get("themes/light.toml").unwrap())
.expect("Failed to parse built-in theme"),
),
})
}
}
impl Theme {
pub fn parse(source: impl AsRef<[u8]>) -> Result<Self> {
#[derive(Deserialize)]
struct ThemeToml {
#[serde(default)]
syntax: HashMap<String, StyleToml>,
#[serde(default)]
ui: HashMap<String, u32>,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum StyleToml {
Color(u32),
Full {
color: Option<u32>,
weight: Option<toml::Value>,
#[serde(default)]
italic: bool,
},
}
let theme_toml: ThemeToml =
toml::from_slice(source.as_ref()).context("failed to parse theme TOML")?;
let mut syntax_styles = Vec::<(String, ColorU, FontProperties)>::new();
for (key, style) in theme_toml.syntax {
let (color, weight, italic) = match style {
StyleToml::Color(color) => (color, None, false),
StyleToml::Full {
color,
weight,
italic,
} => (color.unwrap_or(0), weight, italic),
};
match syntax_styles.binary_search_by_key(&&key, |e| &e.0) {
Ok(i) | Err(i) => {
let mut properties = FontProperties::new();
properties.weight = deserialize_weight(weight)?;
if italic {
properties.style = FontStyle::Italic;
}
syntax_styles.insert(i, (key, deserialize_color(color), properties));
}
}
}
let background_color = theme_toml
.ui
.get("background")
.copied()
.map_or(ColorU::from_u32(0xffffffff), deserialize_color);
let line_number_color = theme_toml
.ui
.get("line_numbers")
.copied()
.map_or(ColorU::black(), deserialize_color);
let default_text_color = theme_toml
.ui
.get("text")
.copied()
.map_or(ColorU::black(), deserialize_color);
Ok(Theme {
background_color,
line_number_color,
default_text_color,
syntax_styles,
})
}
pub fn syntax_style(&self, id: StyleId) -> (ColorU, FontProperties) {
self.syntax_styles
.get(id.0 as usize)
.map_or((self.default_text_color, FontProperties::new()), |entry| {
(entry.1, entry.2)
})
}
#[cfg(test)]
pub fn syntax_style_name(&self, id: StyleId) -> Option<&str> {
self.syntax_styles.get(id.0 as usize).map(|e| e.0.as_str())
}
}
impl ThemeMap {
pub fn new(capture_names: &[String], theme: &Theme) -> Self {
// For each capture name in the highlight query, find the longest
// key in the theme's syntax styles that matches all of the
// dot-separated components of the capture name.
ThemeMap(
capture_names
.iter()
.map(|capture_name| {
theme
.syntax_styles
.iter()
.enumerate()
.filter_map(|(i, (key, _, _))| {
let mut len = 0;
let capture_parts = capture_name.split('.');
for key_part in key.split('.') {
if capture_parts.clone().any(|part| part == key_part) {
len += 1;
} else {
return None;
}
}
Some((i, len))
})
.max_by_key(|(_, len)| *len)
.map_or(DEFAULT_STYLE_ID, |(i, _)| StyleId(i as u32))
})
.collect(),
)
}
pub fn get(&self, capture_id: u32) -> StyleId {
self.0
.get(capture_id as usize)
.copied()
.unwrap_or(DEFAULT_STYLE_ID)
}
}
impl Default for ThemeMap {
fn default() -> Self {
Self(Arc::new([]))
}
}
impl Default for StyleId {
fn default() -> Self {
DEFAULT_STYLE_ID
}
}
pub fn channel(
font_cache: &FontCache,
) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
Ok(watch::channel_with(Settings::new(font_cache)?))
}
fn deserialize_color(color: u32) -> ColorU {
ColorU::from_u32((color << 8) + 0xFF)
}
fn deserialize_weight(weight: Option<toml::Value>) -> Result<FontWeight> {
match &weight {
None => return Ok(FontWeight::NORMAL),
Some(toml::Value::Integer(i)) => return Ok(FontWeight(*i as f32)),
Some(toml::Value::String(s)) => match s.as_str() {
"normal" => return Ok(FontWeight::NORMAL),
"bold" => return Ok(FontWeight::BOLD),
"light" => return Ok(FontWeight::LIGHT),
"semibold" => return Ok(FontWeight::SEMIBOLD),
_ => {}
},
_ => {}
}
Err(anyhow!("Invalid weight {}", weight.unwrap()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_theme() {
let theme = Theme::parse(
r#"
[ui]
background = 0x00ed00
line_numbers = 0xdddddd
[syntax]
"beta.two" = 0xAABBCC
"alpha.one" = {color = 0x112233, weight = "bold"}
"gamma.three" = {weight = "light", italic = true}
"#,
)
.unwrap();
assert_eq!(theme.background_color, ColorU::from_u32(0x00ED00FF));
assert_eq!(theme.line_number_color, ColorU::from_u32(0xddddddff));
assert_eq!(
theme.syntax_styles,
&[
(
"alpha.one".to_string(),
ColorU::from_u32(0x112233FF),
*FontProperties::new().weight(FontWeight::BOLD)
),
(
"beta.two".to_string(),
ColorU::from_u32(0xAABBCCFF),
*FontProperties::new().weight(FontWeight::NORMAL)
),
(
"gamma.three".to_string(),
ColorU::from_u32(0x000000FF),
*FontProperties::new()
.weight(FontWeight::LIGHT)
.style(FontStyle::Italic),
),
]
);
}
#[test]
fn test_parse_empty_theme() {
Theme::parse("").unwrap();
}
#[test]
fn test_theme_map() {
let theme = Theme {
default_text_color: Default::default(),
background_color: ColorU::default(),
line_number_color: ColorU::default(),
syntax_styles: [
("function", ColorU::from_u32(0x100000ff)),
("function.method", ColorU::from_u32(0x200000ff)),
("function.async", ColorU::from_u32(0x300000ff)),
("variable.builtin.self.rust", ColorU::from_u32(0x400000ff)),
("variable.builtin", ColorU::from_u32(0x500000ff)),
("variable", ColorU::from_u32(0x600000ff)),
]
.iter()
.map(|e| (e.0.to_string(), e.1, FontProperties::new()))
.collect(),
};
let capture_names = &[
"function.special".to_string(),
"function.async.rust".to_string(),
"variable.builtin.self".to_string(),
];
let map = ThemeMap::new(capture_names, &theme);
assert_eq!(theme.syntax_style_name(map.get(0)), Some("function"));
assert_eq!(theme.syntax_style_name(map.get(1)), Some("function.async"));
assert_eq!(
theme.syntax_style_name(map.get(2)),
Some("variable.builtin")
);
}
}
+12 -1
View File
@@ -1,9 +1,11 @@
use crate::time::ReplicaId;
use crate::{language::LanguageRegistry, settings, time::ReplicaId, AppState};
use ctor::ctor;
use gpui::AppContext;
use rand::Rng;
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
sync::Arc,
};
use tempdir::TempDir;
@@ -141,3 +143,12 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
panic!("You must pass a JSON object to this helper")
}
}
pub fn build_app_state(ctx: &AppContext) -> AppState {
let settings = settings::channel(&ctx.font_cache()).unwrap().1;
let language_registry = Arc::new(LanguageRegistry::new());
AppState {
settings,
language_registry,
}
}
+47 -21
View File
@@ -2,9 +2,11 @@ pub mod pane;
pub mod pane_group;
use crate::{
editor::{Buffer, BufferView},
language::LanguageRegistry,
settings::Settings,
time::ReplicaId,
worktree::{FileHandle, Worktree, WorktreeHandle},
AppState,
};
use futures_core::Future;
use gpui::{
@@ -40,11 +42,11 @@ pub fn init(app: &mut MutableAppContext) {
pub struct OpenParams {
pub paths: Vec<PathBuf>,
pub settings: watch::Receiver<Settings>,
pub app_state: AppState,
}
fn open(settings: &watch::Receiver<Settings>, ctx: &mut MutableAppContext) {
let settings = settings.clone();
fn open(app_state: &AppState, ctx: &mut MutableAppContext) {
let app_state = app_state.clone();
ctx.prompt_for_paths(
PathPromptOptions {
files: true,
@@ -53,7 +55,7 @@ fn open(settings: &watch::Receiver<Settings>, ctx: &mut MutableAppContext) {
},
move |paths, ctx| {
if let Some(paths) = paths {
ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, settings });
ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, app_state });
}
},
);
@@ -84,7 +86,12 @@ fn open_paths(params: &OpenParams, app: &mut MutableAppContext) {
// Add a new workspace if necessary
app.add_window(|ctx| {
let mut view = Workspace::new(0, params.settings.clone(), ctx);
let mut view = Workspace::new(
0,
params.app_state.settings.clone(),
params.app_state.language_registry.clone(),
ctx,
);
let open_paths = view.open_paths(&params.paths, ctx);
ctx.foreground().spawn(open_paths).detach();
view
@@ -284,6 +291,7 @@ pub struct State {
pub struct Workspace {
pub settings: watch::Receiver<Settings>,
language_registry: Arc<LanguageRegistry>,
modal: Option<AnyViewHandle>,
center: PaneGroup,
panes: Vec<ViewHandle<Pane>>,
@@ -301,6 +309,7 @@ impl Workspace {
pub fn new(
replica_id: ReplicaId,
settings: watch::Receiver<Settings>,
language_registry: Arc<LanguageRegistry>,
ctx: &mut ViewContext<Self>,
) -> Self {
let pane = ctx.add_view(|_| Pane::new(settings.clone()));
@@ -316,6 +325,7 @@ impl Workspace {
panes: vec![pane.clone()],
active_pane: pane.clone(),
settings,
language_registry,
replica_id,
worktrees: Default::default(),
items: Default::default(),
@@ -503,6 +513,7 @@ impl Workspace {
let (mut tx, rx) = postage::watch::channel();
entry.insert(rx);
let replica_id = self.replica_id;
let language_registry = self.language_registry.clone();
ctx.as_mut()
.spawn(|mut ctx| async move {
@@ -512,7 +523,14 @@ impl Workspace {
*tx.borrow_mut() = Some(match history {
Ok(history) => Ok(Box::new(ctx.add_model(|ctx| {
Buffer::from_history(replica_id, history, Some(file), ctx)
let language = language_registry.select_language(path);
Buffer::from_history(
replica_id,
history,
Some(file),
language.cloned(),
ctx,
)
}))),
Err(error) => Err(Arc::new(error)),
})
@@ -757,14 +775,17 @@ impl WorkspaceHandle for ViewHandle<Workspace> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{editor::BufferView, settings, test::temp_tree};
use crate::{
editor::BufferView,
test::{build_app_state, temp_tree},
};
use serde_json::json;
use std::{collections::HashSet, fs};
use tempdir::TempDir;
#[gpui::test]
fn test_open_paths_action(app: &mut gpui::MutableAppContext) {
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = build_app_state(app.as_ref());
init(app);
@@ -790,7 +811,7 @@ mod tests {
dir.path().join("a").to_path_buf(),
dir.path().join("b").to_path_buf(),
],
settings: settings.clone(),
app_state: app_state.clone(),
},
);
assert_eq!(app.window_ids().count(), 1);
@@ -799,7 +820,7 @@ mod tests {
"workspace:open_paths",
OpenParams {
paths: vec![dir.path().join("a").to_path_buf()],
settings: settings.clone(),
app_state: app_state.clone(),
},
);
assert_eq!(app.window_ids().count(), 1);
@@ -815,7 +836,7 @@ mod tests {
dir.path().join("b").to_path_buf(),
dir.path().join("c").to_path_buf(),
],
settings: settings.clone(),
app_state: app_state.clone(),
},
);
assert_eq!(app.window_ids().count(), 2);
@@ -831,10 +852,11 @@ mod tests {
},
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir.path(), ctx);
workspace
});
@@ -935,9 +957,10 @@ mod tests {
"b.txt": "",
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir1.path(), ctx);
workspace
});
@@ -1003,9 +1026,10 @@ mod tests {
"a.txt": "",
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (window_id, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir.path(), ctx);
workspace
});
@@ -1046,9 +1070,10 @@ mod tests {
#[gpui::test]
async fn test_open_and_save_new_file(mut app: gpui::TestAppContext) {
let dir = TempDir::new("test-new-file").unwrap();
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir.path(), ctx);
workspace
});
@@ -1150,9 +1175,10 @@ mod tests {
},
}));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let app_state = app.read(build_app_state);
let (window_id, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
let mut workspace =
Workspace::new(0, app_state.settings, app_state.language_registry, ctx);
workspace.add_worktree(dir.path(), ctx);
workspace
});
+3 -2
View File
@@ -1483,7 +1483,8 @@ mod tests {
let path = tree.update(&mut app, |tree, ctx| {
let path = tree.files(0).next().unwrap().path().clone();
assert_eq!(path.file_name().unwrap(), "file1");
smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
smol::block_on(tree.save(&path, buffer.read(ctx).snapshot().text(), ctx.as_ref()))
.unwrap();
path
});
@@ -1512,7 +1513,7 @@ mod tests {
let file = app.update(|ctx| tree.file("", ctx)).await;
app.update(|ctx| {
assert_eq!(file.path().file_name(), None);
smol::block_on(file.save(buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
smol::block_on(file.save(buffer.read(ctx).snapshot().text(), ctx.as_ref())).unwrap();
});
let history = app.read(|ctx| file.load_history(ctx)).await.unwrap();