logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree with a 3-file patch applied (no upstream history). Patch (crates/gpui_linux/src/linux/wayland/): - serial.rs: add SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; add latest_serial_of() - window.rs: activate() uses keyboard-enter serial (Mutter focus gate) Mutter honors window activation only when the token carries the keyboard- focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial. See docs/tray-window-focus-wayland.md in logiguard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
667
crates/languages/src/c.rs
Normal file
667
crates/languages/src/c.rs
Normal file
@@ -0,0 +1,667 @@
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, AsyncApp};
|
||||
use http_client::github::{AssetKind, GitHubLspBinaryVersion, latest_github_release};
|
||||
use http_client::github_download::{GithubBinaryMetadata, download_server_binary};
|
||||
pub use language::*;
|
||||
use lsp::{InitializeParams, LanguageServerBinary, LanguageServerName};
|
||||
use project::lsp_store::clangd_ext;
|
||||
use serde_json::json;
|
||||
use smol::fs;
|
||||
use std::{env::consts, future::Future, path::PathBuf, sync::Arc};
|
||||
use util::{ResultExt, fs::remove_matching, maybe, merge_json_value_into};
|
||||
|
||||
pub struct CLspAdapter;
|
||||
|
||||
impl CLspAdapter {
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("clangd");
|
||||
}
|
||||
|
||||
impl LspInstaller for CLspAdapter {
|
||||
type BinaryVersion = GitHubLspBinaryVersion;
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
pre_release: bool,
|
||||
_: &mut AsyncApp,
|
||||
) -> Result<GitHubLspBinaryVersion> {
|
||||
ensure_arch_compatibility()?;
|
||||
|
||||
let release =
|
||||
latest_github_release("clangd/clangd", true, pre_release, delegate.http_client())
|
||||
.await?;
|
||||
let os_suffix = match consts::OS {
|
||||
"macos" => "mac",
|
||||
"linux" => "linux",
|
||||
"windows" => "windows",
|
||||
other => bail!("Running on unsupported os: {other}"),
|
||||
};
|
||||
let asset_name = format!("clangd-{}-{}.zip", os_suffix, release.tag_name);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.with_context(|| format!("no asset found matching {asset_name:?}"))?;
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: release.tag_name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
digest: asset.digest.clone(),
|
||||
};
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
async fn check_if_user_installed(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
_: Option<Toolchain>,
|
||||
_: &AsyncApp,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
|
||||
Some(LanguageServerBinary {
|
||||
path,
|
||||
arguments: Vec::new(),
|
||||
env: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn fetch_server_binary(
|
||||
&self,
|
||||
version: GitHubLspBinaryVersion,
|
||||
container_dir: PathBuf,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
|
||||
let delegate = delegate.clone();
|
||||
|
||||
async move {
|
||||
ensure_arch_compatibility()?;
|
||||
|
||||
let GitHubLspBinaryVersion {
|
||||
name,
|
||||
url,
|
||||
digest: expected_digest,
|
||||
} = version;
|
||||
let version_dir = container_dir.join(format!("clangd_{name}"));
|
||||
let binary_path = version_dir
|
||||
.join("bin")
|
||||
.join(format!("clangd{}", consts::EXE_SUFFIX));
|
||||
|
||||
let binary = LanguageServerBinary {
|
||||
path: binary_path.clone(),
|
||||
env: None,
|
||||
arguments: Default::default(),
|
||||
};
|
||||
|
||||
let metadata_path = version_dir.join("metadata");
|
||||
let metadata = GithubBinaryMetadata::read_from_file(&metadata_path)
|
||||
.await
|
||||
.ok();
|
||||
if let Some(metadata) = metadata {
|
||||
let validity_check = async || {
|
||||
delegate
|
||||
.try_exec(LanguageServerBinary {
|
||||
path: binary_path.clone(),
|
||||
arguments: vec!["--version".into()],
|
||||
env: None,
|
||||
})
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
log::warn!(
|
||||
"Unable to run {binary_path:?} asset, redownloading: {err:#}",
|
||||
)
|
||||
})
|
||||
};
|
||||
if let (Some(actual_digest), Some(expected_digest)) =
|
||||
(&metadata.digest, &expected_digest)
|
||||
{
|
||||
if actual_digest == expected_digest {
|
||||
if validity_check().await.is_ok() {
|
||||
return Ok(binary);
|
||||
}
|
||||
} else {
|
||||
log::info!(
|
||||
"SHA-256 mismatch for {binary_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
|
||||
);
|
||||
}
|
||||
} else if validity_check().await.is_ok() {
|
||||
return Ok(binary);
|
||||
}
|
||||
}
|
||||
download_server_binary(
|
||||
&*delegate.http_client(),
|
||||
&url,
|
||||
expected_digest.as_deref(),
|
||||
&container_dir,
|
||||
AssetKind::Zip,
|
||||
)
|
||||
.await?;
|
||||
remove_matching(&container_dir, |entry| entry != version_dir).await;
|
||||
GithubBinaryMetadata::write_to_file(
|
||||
&GithubBinaryMetadata {
|
||||
metadata_version: 1,
|
||||
digest: expected_digest,
|
||||
},
|
||||
&metadata_path,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(binary)
|
||||
}
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir).await
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_arch_compatibility() -> Result<()> {
|
||||
let arch = consts::ARCH;
|
||||
if consts::OS == "linux" && !["x86_64", "x86"].contains(&arch) {
|
||||
anyhow::bail!(
|
||||
"Clangd does not provide prebuilt binaries for {arch} to fetch from GitHub. Consider installing the binary manually."
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl super::LspAdapter for CLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
Self::SERVER_NAME
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::CompletionItem,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
let label_detail = match &completion.label_details {
|
||||
Some(label_detail) => match &label_detail.detail {
|
||||
Some(detail) => detail.trim(),
|
||||
None => "",
|
||||
},
|
||||
None => "",
|
||||
};
|
||||
|
||||
let mut label = completion
|
||||
.label
|
||||
.strip_prefix('•')
|
||||
.unwrap_or(&completion.label)
|
||||
.trim()
|
||||
.to_owned();
|
||||
|
||||
if !label_detail.is_empty() {
|
||||
let should_add_space = match completion.kind {
|
||||
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD) => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if should_add_space && !label.ends_with(' ') && !label_detail.starts_with(' ') {
|
||||
label.push(' ');
|
||||
}
|
||||
label.push_str(label_detail);
|
||||
}
|
||||
|
||||
match completion.kind {
|
||||
Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => {
|
||||
let detail = completion.detail.as_ref().unwrap();
|
||||
let text = format!("{} {}", detail, label);
|
||||
let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
|
||||
let runs = language.highlight_text(&source, 11..11 + text.len());
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
.as_deref()
|
||||
.and_then(|filter_text| {
|
||||
text.find(filter_text)
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(detail.len() + 1..text.len());
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
|
||||
if completion.detail.is_some() =>
|
||||
{
|
||||
let detail = completion.detail.as_ref().unwrap();
|
||||
let text = format!("{} {}", detail, label);
|
||||
let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
.as_deref()
|
||||
.and_then(|filter_text| {
|
||||
text.find(filter_text)
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or(detail.len() + 1..text.len());
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)
|
||||
if completion.detail.is_some() =>
|
||||
{
|
||||
let detail = completion.detail.as_ref().unwrap();
|
||||
let text = format!("{} {}", detail, label);
|
||||
let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
|
||||
let filter_range = completion
|
||||
.filter_text
|
||||
.as_deref()
|
||||
.and_then(|filter_text| {
|
||||
text.find(filter_text)
|
||||
.map(|start| start..start + filter_text.len())
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let filter_start = detail.len() + 1;
|
||||
let filter_end = text
|
||||
.rfind('(')
|
||||
.filter(|end| *end > filter_start)
|
||||
.unwrap_or(text.len());
|
||||
filter_start..filter_end
|
||||
});
|
||||
|
||||
return Some(CodeLabel::new(text, filter_range, runs));
|
||||
}
|
||||
Some(kind) => {
|
||||
let highlight_name = match kind {
|
||||
lsp::CompletionItemKind::STRUCT
|
||||
| lsp::CompletionItemKind::INTERFACE
|
||||
| lsp::CompletionItemKind::CLASS
|
||||
| lsp::CompletionItemKind::ENUM => Some("type"),
|
||||
lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"),
|
||||
lsp::CompletionItemKind::KEYWORD => Some("keyword"),
|
||||
lsp::CompletionItemKind::VALUE | lsp::CompletionItemKind::CONSTANT => {
|
||||
Some("constant")
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(highlight_id) = language
|
||||
.grammar()
|
||||
.and_then(|g| g.highlight_id_for_name(highlight_name?))
|
||||
{
|
||||
let mut label = CodeLabel::plain(label, completion.filter_text.as_deref());
|
||||
label.runs.push((
|
||||
0..label.text.rfind('(').unwrap_or(label.text.len()),
|
||||
highlight_id,
|
||||
));
|
||||
return Some(label);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(CodeLabel::plain(label, completion.filter_text.as_deref()))
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
&self,
|
||||
symbol: &language::Symbol,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
let name = &symbol.name;
|
||||
let (text, filter_range, display_range) = match symbol.kind {
|
||||
lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
|
||||
let text = format!("void {} () {{}}", name);
|
||||
let filter_range = 0..name.len();
|
||||
let display_range = 5..5 + name.len();
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::STRUCT => {
|
||||
let text = format!("struct {} {{}}", name);
|
||||
let filter_range = 7..7 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::ENUM => {
|
||||
let text = format!("enum {} {{}}", name);
|
||||
let filter_range = 5..5 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::INTERFACE | lsp::SymbolKind::CLASS => {
|
||||
let text = format!("class {} {{}}", name);
|
||||
let filter_range = 6..6 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::CONSTANT => {
|
||||
let text = format!("const int {} = 0;", name);
|
||||
let filter_range = 10..10 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::MODULE => {
|
||||
let text = format!("namespace {} {{}}", name);
|
||||
let filter_range = 10..10 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
lsp::SymbolKind::TYPE_PARAMETER => {
|
||||
let text = format!("typename {} {{}};", name);
|
||||
let filter_range = 9..9 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(CodeLabel::new(
|
||||
text[display_range.clone()].to_string(),
|
||||
filter_range,
|
||||
language.highlight_text(&text.as_str().into(), display_range),
|
||||
))
|
||||
}
|
||||
|
||||
fn prepare_initialize_params(
|
||||
&self,
|
||||
mut original: InitializeParams,
|
||||
_: &App,
|
||||
) -> Result<InitializeParams> {
|
||||
let experimental = json!({
|
||||
"textDocument": {
|
||||
"completion" : {
|
||||
// enable clangd's dot-to-arrow feature.
|
||||
"editsNearCursor": true
|
||||
},
|
||||
"inactiveRegionsCapabilities": {
|
||||
"inactiveRegions": true,
|
||||
}
|
||||
}
|
||||
});
|
||||
if let Some(ref mut original_experimental) = original.capabilities.experimental {
|
||||
merge_json_value_into(experimental, original_experimental);
|
||||
} else {
|
||||
original.capabilities.experimental = Some(experimental);
|
||||
}
|
||||
Ok(original)
|
||||
}
|
||||
|
||||
fn retain_old_diagnostic(&self, previous_diagnostic: &Diagnostic) -> bool {
|
||||
clangd_ext::is_inactive_region(previous_diagnostic)
|
||||
}
|
||||
|
||||
fn underline_diagnostic(&self, diagnostic: &lsp::Diagnostic) -> bool {
|
||||
!clangd_ext::is_lsp_inactive_region(diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
maybe!(async {
|
||||
let mut last_clangd_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_clangd_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let clangd_dir = last_clangd_dir.context("no cached binary")?;
|
||||
let clangd_bin = clangd_dir
|
||||
.join("bin")
|
||||
.join(format!("clangd{}", consts::EXE_SUFFIX));
|
||||
anyhow::ensure!(
|
||||
clangd_bin.exists(),
|
||||
"missing clangd binary in directory {clangd_dir:?}"
|
||||
);
|
||||
Ok(LanguageServerBinary {
|
||||
path: clangd_bin,
|
||||
env: None,
|
||||
arguments: Vec::new(),
|
||||
})
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{AppContext as _, BorrowAppContext, TestAppContext};
|
||||
use language::{AutoindentMode, Buffer};
|
||||
use settings::SettingsStore;
|
||||
use std::num::NonZeroU32;
|
||||
use unindent::Unindent;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_c_autoindent_basic(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let test_settings = SettingsStore::test(cx);
|
||||
cx.set_global(test_settings);
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings(cx, |s| {
|
||||
s.project.all_languages.defaults.tab_size = NonZeroU32::new(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
let language = crate::language("c", tree_sitter_c::LANGUAGE.into());
|
||||
|
||||
cx.new(|cx| {
|
||||
let mut buffer = Buffer::local("", cx).with_language(language, cx);
|
||||
|
||||
buffer.edit([(0..0, "int main() {}")], None, cx);
|
||||
|
||||
let ix = buffer.len() - 1;
|
||||
buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"int main() {\n \n}",
|
||||
"content inside braces should be indented"
|
||||
);
|
||||
|
||||
buffer
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_c_autoindent_if_else(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let test_settings = SettingsStore::test(cx);
|
||||
cx.set_global(test_settings);
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings(cx, |s| {
|
||||
s.project.all_languages.defaults.tab_size = NonZeroU32::new(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
let language = crate::language("c", tree_sitter_c::LANGUAGE.into());
|
||||
|
||||
cx.new(|cx| {
|
||||
let mut buffer = Buffer::local("", cx).with_language(language, cx);
|
||||
|
||||
buffer.edit(
|
||||
[(
|
||||
0..0,
|
||||
r#"
|
||||
int main() {
|
||||
if (a)
|
||||
b;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
)],
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
r#"
|
||||
int main() {
|
||||
if (a)
|
||||
b;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
"body of if-statement without braces should be indented"
|
||||
);
|
||||
|
||||
let ix = buffer.len() - 4;
|
||||
buffer.edit([(ix..ix, "\n.c")], Some(AutoindentMode::EachLine), cx);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
r#"
|
||||
int main() {
|
||||
if (a)
|
||||
b
|
||||
.c;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
"field expression (.c) should be indented further than the statement body"
|
||||
);
|
||||
|
||||
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
||||
buffer.edit(
|
||||
[(
|
||||
0..0,
|
||||
r#"
|
||||
int main() {
|
||||
if (a) a++;
|
||||
else b++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
)],
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
r#"
|
||||
int main() {
|
||||
if (a) a++;
|
||||
else b++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
"single-line if/else without braces should align at the same level"
|
||||
);
|
||||
|
||||
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
||||
buffer.edit(
|
||||
[(
|
||||
0..0,
|
||||
r#"
|
||||
int main() {
|
||||
if (a)
|
||||
b++;
|
||||
else
|
||||
c++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
)],
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
r#"
|
||||
int main() {
|
||||
if (a)
|
||||
b++;
|
||||
else
|
||||
c++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
"multi-line if/else without braces should indent statement bodies"
|
||||
);
|
||||
|
||||
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
||||
buffer.edit(
|
||||
[(
|
||||
0..0,
|
||||
r#"
|
||||
int main() {
|
||||
if (a)
|
||||
if (b)
|
||||
c++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
)],
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
r#"
|
||||
int main() {
|
||||
if (a)
|
||||
if (b)
|
||||
c++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
"nested if statements without braces should indent properly"
|
||||
);
|
||||
|
||||
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
||||
buffer.edit(
|
||||
[(
|
||||
0..0,
|
||||
r#"
|
||||
int main() {
|
||||
if (a)
|
||||
b++;
|
||||
else if (c)
|
||||
d++;
|
||||
else
|
||||
f++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
)],
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
r#"
|
||||
int main() {
|
||||
if (a)
|
||||
b++;
|
||||
else if (c)
|
||||
d++;
|
||||
else
|
||||
f++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
"else-if chains should align all conditions at same level with indented bodies"
|
||||
);
|
||||
|
||||
buffer.edit([(0..buffer.len(), "")], Some(AutoindentMode::EachLine), cx);
|
||||
buffer.edit(
|
||||
[(
|
||||
0..0,
|
||||
r#"
|
||||
int main() {
|
||||
if (a) {
|
||||
b++;
|
||||
} else
|
||||
c++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
)],
|
||||
Some(AutoindentMode::EachLine),
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
r#"
|
||||
int main() {
|
||||
if (a) {
|
||||
b++;
|
||||
} else
|
||||
c++;
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
"mixed braces should indent properly"
|
||||
);
|
||||
|
||||
buffer
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user