<commit_before>use std::{
    env,
    fs::File,
    io,
    path::{Path, PathBuf},
};

use flate2::{write::GzEncoder, Compression};
use xshell::{cmd, Shell};

use crate::{date_iso, flags, project_root};

const VERSION_STABLE: &str = "0.3";
const VERSION_NIGHTLY: &str = "0.4";
const VERSION_DEV: &str = "0.5"; // keep this one in sync with `package.json`

impl flags::Dist {
    pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
        let stable = sh.var("GITHUB_REF").unwrap_or_default().as_str() == "refs/heads/release";

        let project_root = project_root();
        let target = Target::get(&project_root);
        let dist = project_root.join("dist");
        sh.remove_path(&dist)?;
        sh.create_dir(&dist)?;

        let release_channel = if stable { "stable" } else { "nightly" };
        dist_server(sh, release_channel, &target)?;

        if let Some(patch_version) = self.client_patch_version {
            let version = if stable {
                format!("{}.{}", VERSION_STABLE, patch_version)
            } else {
                // A hack to make VS Code prefer nightly over stable.
                format!("{}.{}", VERSION_NIGHTLY, patch_version)
            };
            let release_tag = if stable { date_iso(sh)? } else { "nightly".to_string() };
            dist_client(sh, &version, &release_tag, &target)?;
        }
        Ok(())
    }
}

fn dist_client(
    sh: &Shell,
    version: &str,
    release_tag: &str,
    target: &Target,
) -> anyhow::Result<()> {
    let bundle_path = Path::new("editors").join("code").join("server");
    sh.create_dir(&bundle_path)?;
    sh.copy_file(&target.server_path, &bundle_path)?;
    if let Some(symbols_path) = &target.symbols_path {
        sh.copy_file(symbols_path, &bundle_path)?;
    }

    let _d = sh.push_dir("./editors/code");

    let mut patch = Patch::new(sh, "./package.json")?;
    patch
        .replace(
            &format!(r#""version": "{}.0-dev""#, VERSION_DEV),
            &format!(r#""version": "{}""#, version),
        )
        .replace(r#""releaseTag": null"#, &format!(r#""releaseTag": "{}""#, release_tag))
        .replace(r#""$generated-start": {},"#, "")
        .replace(",\n                \"$generated-end\": {}", "")
        .replace(r#""enabledApiProposals": [],"#, r#""#);
    patch.commit(sh)?;

    Ok(())
}

fn dist_server(sh: &Shell, release_channel: &str, target: &Target) -> anyhow::Result<()> {
    let _e = sh.push_env("CFG_RELEASE_CHANNEL", release_channel);
    let _e = sh.push_env("CFG_RELEASE", "0.0.0");
    let _e = sh.push_env("CARGO_PROFILE_RELEASE_LTO", "thin");

    // Uncomment to enable debug info for releases. Note that:
    //   * debug info is split on windows and macs, so it does nothing for those platforms,
    //   * on Linux, this blows up the binary size from 8MB to 43MB, which is unreasonable.
    // let _e = sh.push_env("CARGO_PROFILE_RELEASE_DEBUG", "1");

    if target.name.contains("-linux-") {
        env::set_var("CC", "clang");
    }

    let target_name = &target.name;
    cmd!(sh, "cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} --release").run()?;

    let dst = Path::new("dist").join(&target.artifact_name);
    gzip(&target.server_path, &dst.with_extension("gz"))?;

    Ok(())
}

fn gzip(src_path: &Path, dest_path: &Path) -> anyhow::Result<()> {
    let mut encoder = GzEncoder::new(File::create(dest_path)?, Compression::best());
    let mut input = io::BufReader::new(File::open(src_path)?);
    io::copy(&mut input, &mut encoder)?;
    encoder.finish()?;
    Ok(())
}

struct Target {
    name: String,
    server_path: PathBuf,
    symbols_path: Option<PathBuf>,
    artifact_name: String,
}

impl Target {
    fn get(project_root: &Path) -> Self {
        let name = match env::var("RA_TARGET") {
            Ok(target) => target,
            _ => {
                if cfg!(target_os = "linux") {
                    "x86_64-unknown-linux-gnu".to_string()
                } else if cfg!(target_os = "windows") {
                    "x86_64-pc-windows-msvc".to_string()
                } else if cfg!(target_os = "macos") {
                    "x86_64-apple-darwin".to_string()
                } else {
                    panic!("Unsupported OS, maybe try setting RA_TARGET")
                }
            }
        };
        let out_path = project_root.join("target").join(&name).join("release");
        let (exe_suffix, symbols_path) = if name.contains("-windows-") {
            (".exe".into(), Some(out_path.join("rust_analyzer.pdb")))
        } else {
            (String::new(), None)
        };
        let server_path = out_path.join(format!("rust-analyzer{}", exe_suffix));
        let artifact_name = format!("rust-analyzer-{}{}", name, exe_suffix);
        Self { name, server_path, symbols_path, artifact_name }
    }
}

struct Patch {
    path: PathBuf,
    original_contents: String,
    contents: String,
}

impl Patch {
    fn new(sh: &Shell, path: impl Into<PathBuf>) -> anyhow::Result<Patch> {
        let path = path.into();
        let contents = sh.read_file(&path)?;
        Ok(Patch { path, original_contents: contents.clone(), contents })
    }

    fn replace(&mut self, from: &str, to: &str) -> &mut Patch {
        assert!(self.contents.contains(from));
        self.contents = self.contents.replace(from, to);
        self
    }

    fn commit(&self, sh: &Shell) -> anyhow::Result<()> {
        sh.write_file(&self.path, &self.contents)?;
        Ok(())
    }
}

impl Drop for Patch {
    fn drop(&mut self) {
        // FIXME: find a way to bring this back
        let _ = &self.original_contents;
        // write_file(&self.path, &self.original_contents).unwrap();
    }
}
<commit_msg>Auto merge of #12755 - Veykril:server-version, r=lnicola<commit_after>use std::{
    env,
    fs::File,
    io,
    path::{Path, PathBuf},
};

use flate2::{write::GzEncoder, Compression};
use xshell::{cmd, Shell};

use crate::{date_iso, flags, project_root};

const VERSION_STABLE: &str = "0.3";
const VERSION_NIGHTLY: &str = "0.4";
const VERSION_DEV: &str = "0.5"; // keep this one in sync with `package.json`

impl flags::Dist {
    pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
        let stable = sh.var("GITHUB_REF").unwrap_or_default().as_str() == "refs/heads/release";

        let project_root = project_root();
        let target = Target::get(&project_root);
        let dist = project_root.join("dist");
        sh.remove_path(&dist)?;
        sh.create_dir(&dist)?;

        if let Some(patch_version) = self.client_patch_version {
            let version = if stable {
                format!("{}.{}", VERSION_STABLE, patch_version)
            } else {
                // A hack to make VS Code prefer nightly over stable.
                format!("{}.{}", VERSION_NIGHTLY, patch_version)
            };
            dist_server(sh, &format!("{version}-standalone"), &target)?;
            let release_tag = if stable { date_iso(sh)? } else { "nightly".to_string() };
            dist_client(sh, &version, &release_tag, &target)?;
        } else {
            dist_server(sh, "0.0.0-standalone", &target)?;
        }
        Ok(())
    }
}

fn dist_client(
    sh: &Shell,
    version: &str,
    release_tag: &str,
    target: &Target,
) -> anyhow::Result<()> {
    let bundle_path = Path::new("editors").join("code").join("server");
    sh.create_dir(&bundle_path)?;
    sh.copy_file(&target.server_path, &bundle_path)?;
    if let Some(symbols_path) = &target.symbols_path {
        sh.copy_file(symbols_path, &bundle_path)?;
    }

    let _d = sh.push_dir("./editors/code");

    let mut patch = Patch::new(sh, "./package.json")?;
    patch
        .replace(
            &format!(r#""version": "{}.0-dev""#, VERSION_DEV),
            &format!(r#""version": "{}""#, version),
        )
        .replace(r#""releaseTag": null"#, &format!(r#""releaseTag": "{}""#, release_tag))
        .replace(r#""$generated-start": {},"#, "")
        .replace(",\n                \"$generated-end\": {}", "")
        .replace(r#""enabledApiProposals": [],"#, r#""#);
    patch.commit(sh)?;

    Ok(())
}

fn dist_server(sh: &Shell, release: &str, target: &Target) -> anyhow::Result<()> {
    let _e = sh.push_env("CFG_RELEASE", release);
    let _e = sh.push_env("CARGO_PROFILE_RELEASE_LTO", "thin");

    // Uncomment to enable debug info for releases. Note that:
    //   * debug info is split on windows and macs, so it does nothing for those platforms,
    //   * on Linux, this blows up the binary size from 8MB to 43MB, which is unreasonable.
    // let _e = sh.push_env("CARGO_PROFILE_RELEASE_DEBUG", "1");

    if target.name.contains("-linux-") {
        env::set_var("CC", "clang");
    }

    let target_name = &target.name;
    cmd!(sh, "cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} --release").run()?;

    let dst = Path::new("dist").join(&target.artifact_name);
    gzip(&target.server_path, &dst.with_extension("gz"))?;

    Ok(())
}

fn gzip(src_path: &Path, dest_path: &Path) -> anyhow::Result<()> {
    let mut encoder = GzEncoder::new(File::create(dest_path)?, Compression::best());
    let mut input = io::BufReader::new(File::open(src_path)?);
    io::copy(&mut input, &mut encoder)?;
    encoder.finish()?;
    Ok(())
}

struct Target {
    name: String,
    server_path: PathBuf,
    symbols_path: Option<PathBuf>,
    artifact_name: String,
}

impl Target {
    fn get(project_root: &Path) -> Self {
        let name = match env::var("RA_TARGET") {
            Ok(target) => target,
            _ => {
                if cfg!(target_os = "linux") {
                    "x86_64-unknown-linux-gnu".to_string()
                } else if cfg!(target_os = "windows") {
                    "x86_64-pc-windows-msvc".to_string()
                } else if cfg!(target_os = "macos") {
                    "x86_64-apple-darwin".to_string()
                } else {
                    panic!("Unsupported OS, maybe try setting RA_TARGET")
                }
            }
        };
        let out_path = project_root.join("target").join(&name).join("release");
        let (exe_suffix, symbols_path) = if name.contains("-windows-") {
            (".exe".into(), Some(out_path.join("rust_analyzer.pdb")))
        } else {
            (String::new(), None)
        };
        let server_path = out_path.join(format!("rust-analyzer{}", exe_suffix));
        let artifact_name = format!("rust-analyzer-{}{}", name, exe_suffix);
        Self { name, server_path, symbols_path, artifact_name }
    }
}

struct Patch {
    path: PathBuf,
    original_contents: String,
    contents: String,
}

impl Patch {
    fn new(sh: &Shell, path: impl Into<PathBuf>) -> anyhow::Result<Patch> {
        let path = path.into();
        let contents = sh.read_file(&path)?;
        Ok(Patch { path, original_contents: contents.clone(), contents })
    }

    fn replace(&mut self, from: &str, to: &str) -> &mut Patch {
        assert!(self.contents.contains(from));
        self.contents = self.contents.replace(from, to);
        self
    }

    fn commit(&self, sh: &Shell) -> anyhow::Result<()> {
        sh.write_file(&self.path, &self.contents)?;
        Ok(())
    }
}

impl Drop for Patch {
    fn drop(&mut self) {
        // FIXME: find a way to bring this back
        let _ = &self.original_contents;
        // write_file(&self.path, &self.original_contents).unwrap();
    }
}
<|endoftext|>


<commit_before>use std::{
    env,
    fs::File,
    io,
    path::{Path, PathBuf},
};

use anyhow::Result;
use flate2::{write::GzEncoder, Compression};
use xshell::{cmd, cp, mkdir_p, pushd, pushenv, read_file, rm_rf, write_file};

use crate::{date_iso, flags, project_root};

impl flags::Dist {
    pub(crate) fn run(self) -> Result<()> {
        let stable =
            std::env::var("GITHUB_REF").unwrap_or_default().as_str() == "refs/heads/release";

        let project_root = project_root();
        let target = Target::get(&project_root);
        let dist = project_root.join("dist");
        rm_rf(&dist)?;
        mkdir_p(&dist)?;

        let release_channel = if stable { "stable" } else { "nightly" };
        dist_server(release_channel, &target)?;

        if let Some(patch_version) = self.client_patch_version {
            let version = if stable {
                format!("0.2.{}", patch_version)
            } else {
                // A hack to make VS Code prefer nightly over stable.
                format!("0.3.{}", patch_version)
            };
            let release_tag = if stable { date_iso()? } else { "nightly".to_string() };
            dist_client(&version, &release_tag, &target)?;
        }
        Ok(())
    }
}

fn dist_client(version: &str, release_tag: &str, target: &Target) -> Result<()> {
    let bundle_path = Path::new("editors").join("code").join("server");
    mkdir_p(&bundle_path)?;
    cp(&target.server_path, &bundle_path)?;
    if let Some(symbols_path) = &target.symbols_path {
        cp(symbols_path, &bundle_path)?;
    }

    let _d = pushd("./editors/code")?;
    let nightly = release_tag == "nightly";

    let mut patch = Patch::new("./package.json")?;
    patch
        .replace(r#""version": "0.4.0-dev""#, &format!(r#""version": "{}""#, version))
        .replace(r#""releaseTag": null"#, &format!(r#""releaseTag": "{}""#, release_tag))
        .replace(r#""$generated-start": {},"#, "")
        .replace(",\n                \"$generated-end\": {}", "");

    if nightly {
        patch.replace(
            r#""displayName": "rust-analyzer""#,
            r#""displayName": "rust-analyzer (nightly)""#,
        );
    }
    if !nightly {
        patch.replace(r#""enableProposedApi": true,"#, r#""#);
    }
    patch.commit()?;

    Ok(())
}

fn dist_server(release_channel: &str, target: &Target) -> Result<()> {
    let _e = pushenv("RUST_ANALYZER_CHANNEL", release_channel);
    let _e = pushenv("CARGO_PROFILE_RELEASE_LTO", "thin");

    // Uncomment to enable debug info for releases. Note that:
    //   * debug info is split on windows and macs, so it does nothing for those platforms,
    //   * on Linux, this blows up the binary size from 8MB to 43MB, which is unreasonable.
    // let _e = pushenv("CARGO_PROFILE_RELEASE_DEBUG", "1");

    if target.name.contains("-linux-") {
        env::set_var("CC", "clang");
    }

    let target_name = &target.name;
    cmd!("cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} --release").run()?;

    let dst = Path::new("dist").join(&target.artifact_name);
    gzip(&target.server_path, &dst.with_extension("gz"))?;

    Ok(())
}

fn gzip(src_path: &Path, dest_path: &Path) -> Result<()> {
    let mut encoder = GzEncoder::new(File::create(dest_path)?, Compression::best());
    let mut input = io::BufReader::new(File::open(src_path)?);
    io::copy(&mut input, &mut encoder)?;
    encoder.finish()?;
    Ok(())
}

struct Target {
    name: String,
    server_path: PathBuf,
    symbols_path: Option<PathBuf>,
    artifact_name: String,
}

impl Target {
    fn get(project_root: &Path) -> Self {
        let name = match env::var("RA_TARGET") {
            Ok(target) => target,
            _ => {
                if cfg!(target_os = "linux") {
                    "x86_64-unknown-linux-gnu".to_string()
                } else if cfg!(target_os = "windows") {
                    "x86_64-pc-windows-msvc".to_string()
                } else if cfg!(target_os = "macos") {
                    "x86_64-apple-darwin".to_string()
                } else {
                    panic!("Unsupported OS, maybe try setting RA_TARGET")
                }
            }
        };
        let out_path = project_root.join("target").join(&name).join("release");
        let (exe_suffix, symbols_path) = if name.contains("-windows-") {
            (".exe".into(), Some(out_path.join("rust_analyzer.pdb")))
        } else {
            (String::new(), None)
        };
        let server_path = out_path.join(format!("rust-analyzer{}", exe_suffix));
        let artifact_name = format!("rust-analyzer-{}{}", name, exe_suffix);
        Self { name, server_path, symbols_path, artifact_name }
    }
}

struct Patch {
    path: PathBuf,
    original_contents: String,
    contents: String,
}

impl Patch {
    fn new(path: impl Into<PathBuf>) -> Result<Patch> {
        let path = path.into();
        let contents = read_file(&path)?;
        Ok(Patch { path, original_contents: contents.clone(), contents })
    }

    fn replace(&mut self, from: &str, to: &str) -> &mut Patch {
        assert!(self.contents.contains(from));
        self.contents = self.contents.replace(from, to);
        self
    }

    fn commit(&self) -> Result<()> {
        write_file(&self.path, &self.contents)?;
        Ok(())
    }
}

impl Drop for Patch {
    fn drop(&mut self) {
        // FIXME: find a way to bring this back
        let _ = &self.original_contents;
        // write_file(&self.path, &self.original_contents).unwrap();
    }
}
<commit_msg>Disable unstable APIs for nightly releases<commit_after>use std::{
    env,
    fs::File,
    io,
    path::{Path, PathBuf},
};

use anyhow::Result;
use flate2::{write::GzEncoder, Compression};
use xshell::{cmd, cp, mkdir_p, pushd, pushenv, read_file, rm_rf, write_file};

use crate::{date_iso, flags, project_root};

impl flags::Dist {
    pub(crate) fn run(self) -> Result<()> {
        let stable =
            std::env::var("GITHUB_REF").unwrap_or_default().as_str() == "refs/heads/release";

        let project_root = project_root();
        let target = Target::get(&project_root);
        let dist = project_root.join("dist");
        rm_rf(&dist)?;
        mkdir_p(&dist)?;

        let release_channel = if stable { "stable" } else { "nightly" };
        dist_server(release_channel, &target)?;

        if let Some(patch_version) = self.client_patch_version {
            let version = if stable {
                format!("0.2.{}", patch_version)
            } else {
                // A hack to make VS Code prefer nightly over stable.
                format!("0.3.{}", patch_version)
            };
            let release_tag = if stable { date_iso()? } else { "nightly".to_string() };
            dist_client(&version, &release_tag, &target)?;
        }
        Ok(())
    }
}

fn dist_client(version: &str, release_tag: &str, target: &Target) -> Result<()> {
    let bundle_path = Path::new("editors").join("code").join("server");
    mkdir_p(&bundle_path)?;
    cp(&target.server_path, &bundle_path)?;
    if let Some(symbols_path) = &target.symbols_path {
        cp(symbols_path, &bundle_path)?;
    }

    let _d = pushd("./editors/code")?;
    let nightly = release_tag == "nightly";

    let mut patch = Patch::new("./package.json")?;
    patch
        .replace(r#""version": "0.4.0-dev""#, &format!(r#""version": "{}""#, version))
        .replace(r#""releaseTag": null"#, &format!(r#""releaseTag": "{}""#, release_tag))
        .replace(r#""$generated-start": {},"#, "")
        .replace(",\n                \"$generated-end\": {}", "");

    if nightly {
        patch.replace(
            r#""displayName": "rust-analyzer""#,
            r#""displayName": "rust-analyzer (nightly)""#,
        );
    }
    patch.replace(r#""enableProposedApi": true,"#, r#""#);
    patch.commit()?;

    Ok(())
}

fn dist_server(release_channel: &str, target: &Target) -> Result<()> {
    let _e = pushenv("RUST_ANALYZER_CHANNEL", release_channel);
    let _e = pushenv("CARGO_PROFILE_RELEASE_LTO", "thin");

    // Uncomment to enable debug info for releases. Note that:
    //   * debug info is split on windows and macs, so it does nothing for those platforms,
    //   * on Linux, this blows up the binary size from 8MB to 43MB, which is unreasonable.
    // let _e = pushenv("CARGO_PROFILE_RELEASE_DEBUG", "1");

    if target.name.contains("-linux-") {
        env::set_var("CC", "clang");
    }

    let target_name = &target.name;
    cmd!("cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} --release").run()?;

    let dst = Path::new("dist").join(&target.artifact_name);
    gzip(&target.server_path, &dst.with_extension("gz"))?;

    Ok(())
}

fn gzip(src_path: &Path, dest_path: &Path) -> Result<()> {
    let mut encoder = GzEncoder::new(File::create(dest_path)?, Compression::best());
    let mut input = io::BufReader::new(File::open(src_path)?);
    io::copy(&mut input, &mut encoder)?;
    encoder.finish()?;
    Ok(())
}

struct Target {
    name: String,
    server_path: PathBuf,
    symbols_path: Option<PathBuf>,
    artifact_name: String,
}

impl Target {
    fn get(project_root: &Path) -> Self {
        let name = match env::var("RA_TARGET") {
            Ok(target) => target,
            _ => {
                if cfg!(target_os = "linux") {
                    "x86_64-unknown-linux-gnu".to_string()
                } else if cfg!(target_os = "windows") {
                    "x86_64-pc-windows-msvc".to_string()
                } else if cfg!(target_os = "macos") {
                    "x86_64-apple-darwin".to_string()
                } else {
                    panic!("Unsupported OS, maybe try setting RA_TARGET")
                }
            }
        };
        let out_path = project_root.join("target").join(&name).join("release");
        let (exe_suffix, symbols_path) = if name.contains("-windows-") {
            (".exe".into(), Some(out_path.join("rust_analyzer.pdb")))
        } else {
            (String::new(), None)
        };
        let server_path = out_path.join(format!("rust-analyzer{}", exe_suffix));
        let artifact_name = format!("rust-analyzer-{}{}", name, exe_suffix);
        Self { name, server_path, symbols_path, artifact_name }
    }
}

struct Patch {
    path: PathBuf,
    original_contents: String,
    contents: String,
}

impl Patch {
    fn new(path: impl Into<PathBuf>) -> Result<Patch> {
        let path = path.into();
        let contents = read_file(&path)?;
        Ok(Patch { path, original_contents: contents.clone(), contents })
    }

    fn replace(&mut self, from: &str, to: &str) -> &mut Patch {
        assert!(self.contents.contains(from));
        self.contents = self.contents.replace(from, to);
        self
    }

    fn commit(&self) -> Result<()> {
        write_file(&self.path, &self.contents)?;
        Ok(())
    }
}

impl Drop for Patch {
    fn drop(&mut self) {
        // FIXME: find a way to bring this back
        let _ = &self.original_contents;
        // write_file(&self.path, &self.original_contents).unwrap();
    }
}
<|endoftext|>


<commit_before>// Copyright (C) 2017, 2018 O.S. Systems Sofware LTDA
//
// SPDX-License-Identifier: Apache-2.0
use argh::FromArgs;
use slog_scope::info;
use std::path::PathBuf;

#[derive(FromArgs)]
/// Top-level command.
struct TopLevel {
    #[argh(subcommand)]
    entry_point: EntryPoints,
}

#[derive(FromArgs)]
#[argh(subcommand)]
enum EntryPoints {
    Client(ClientOptions),
    Server(ServerOptions),
}

#[derive(FromArgs)]
/// Client subcommand
#[argh(subcommand, name = "client")]
struct ClientOptions {
    #[argh(subcommand)]
    commands: ClientCommands,
}

#[derive(FromArgs)]
#[argh(subcommand)]
enum ClientCommands {
    Info(Info),
    Log(Log),
    Probe(Probe),
    AbortDownload(AbortDownload),
    LocalInstall(LocalInstall),
    RemoteInstall(RemoteInstall),
}

#[derive(FromArgs)]
/// Fetches information about the current state of the agent
#[argh(subcommand, name = "info")]
struct Info {}

#[derive(FromArgs)]
/// Fetches the available log entries for the last update cycle
#[argh(subcommand, name = "log")]
struct Log {}

#[derive(FromArgs)]
/// Checks if the server has a new update for this device.
///
/// A custom server for the update cycle can be specified via the ´--server´
#[argh(subcommand, name = "probe")]
struct Probe {
    /// custom address to try probe
    #[argh(option)]
    server: Option<String>,
}

#[derive(FromArgs)]
/// Abort current running download
#[argh(subcommand, name = "abort-download")]
struct AbortDownload {}

#[derive(FromArgs)]
/// Request agent to install a local update package
#[argh(subcommand, name = "local-install")]
struct LocalInstall {
    /// path to the update package
    #[argh(positional)]
    file: PathBuf,
}

#[derive(FromArgs)]
/// Request agent to download and install a package from a direct URL
#[argh(subcommand, name = "remote-install")]
struct RemoteInstall {
    /// the URL to get the update package
    #[argh(positional)]
    url: String,
}

#[derive(FromArgs)]
/// Server subcommand
#[argh(subcommand, name = "server")]
struct ServerOptions {
    /// increase the verboseness level
    #[argh(option, short = 'v', from_str_fn(verbosity_level))]
    verbosity: Option<slog::Level>,

    /// specify the configuration file to use. Default to "/etc/updatehub.conf"
    #[argh(option, short = 'c', default = "PathBuf::from(\"/etc/updatehub.conf\")")]
    conf_file: PathBuf,
}

fn verbosity_level(value: &str) -> Result<slog::Level, String> {
    use std::str::FromStr;
    slog::Level::from_str(value).map_err(|_| format!("failed to parse verbosity level: {}", value))
}

async fn server_main(cmd: ServerOptions) -> updatehub::Result<()> {
    updatehub::logger::init(cmd.verbosity.unwrap_or(slog::Level::Info));
    info!("starting UpdateHub Agent {}", updatehub::version());

    let settings = updatehub::Settings::load(&cmd.conf_file)?;

    updatehub::run(settings).await?;

    Ok(())
}

async fn client_main(cmd: ClientCommands) -> updatehub::Result<()> {
    let client = sdk::Client::new("localhost:8080");

    match cmd {
        ClientCommands::Info(_) => println!("{:#?}", client.info().await),
        ClientCommands::Log(_) => println!("{:#?}", client.log().await),
        ClientCommands::Probe(Probe { server }) => println!("{:#?}", client.probe(server).await),
        ClientCommands::AbortDownload(_) => println!("{:#?}", client.abort_download().await),
        ClientCommands::LocalInstall(LocalInstall { file }) => {
            let file =
                if file.is_absolute() { file } else { std::env::current_dir().unwrap().join(file) };
            println!("{:#?}", client.local_install(&file).await)
        }
        ClientCommands::RemoteInstall(RemoteInstall { url }) => {
            println!("{:#?}", client.remote_install(&url).await)
        }
    }

    Ok(())
}

#[actix_rt::main]
async fn main() {
    let cmd: TopLevel = argh::from_env();

    let res = match cmd.entry_point {
        EntryPoints::Client(client) => client_main(client.commands).await,
        EntryPoints::Server(cmd) => server_main(cmd).await,
    };

    if let Err(e) = res {
        eprintln!("{}", e);
        std::process::exit(1);
    }
}
<commit_msg>updatehub: Simplify parsing of 'verbosity' cmdline option<commit_after>// Copyright (C) 2017, 2018 O.S. Systems Sofware LTDA
//
// SPDX-License-Identifier: Apache-2.0
use argh::FromArgs;
use slog_scope::info;
use std::path::PathBuf;

#[derive(FromArgs)]
/// Top-level command.
struct TopLevel {
    #[argh(subcommand)]
    entry_point: EntryPoints,
}

#[derive(FromArgs)]
#[argh(subcommand)]
enum EntryPoints {
    Client(ClientOptions),
    Server(ServerOptions),
}

#[derive(FromArgs)]
/// Client subcommand
#[argh(subcommand, name = "client")]
struct ClientOptions {
    #[argh(subcommand)]
    commands: ClientCommands,
}

#[derive(FromArgs)]
#[argh(subcommand)]
enum ClientCommands {
    Info(Info),
    Log(Log),
    Probe(Probe),
    AbortDownload(AbortDownload),
    LocalInstall(LocalInstall),
    RemoteInstall(RemoteInstall),
}

#[derive(FromArgs)]
/// Fetches information about the current state of the agent
#[argh(subcommand, name = "info")]
struct Info {}

#[derive(FromArgs)]
/// Fetches the available log entries for the last update cycle
#[argh(subcommand, name = "log")]
struct Log {}

#[derive(FromArgs)]
/// Checks if the server has a new update for this device.
///
/// A custom server for the update cycle can be specified via the ´--server´
#[argh(subcommand, name = "probe")]
struct Probe {
    /// custom address to try probe
    #[argh(option)]
    server: Option<String>,
}

#[derive(FromArgs)]
/// Abort current running download
#[argh(subcommand, name = "abort-download")]
struct AbortDownload {}

#[derive(FromArgs)]
/// Request agent to install a local update package
#[argh(subcommand, name = "local-install")]
struct LocalInstall {
    /// path to the update package
    #[argh(positional)]
    file: PathBuf,
}

#[derive(FromArgs)]
/// Request agent to download and install a package from a direct URL
#[argh(subcommand, name = "remote-install")]
struct RemoteInstall {
    /// the URL to get the update package
    #[argh(positional)]
    url: String,
}

#[derive(FromArgs)]
/// Server subcommand
#[argh(subcommand, name = "server")]
struct ServerOptions {
    /// increase the verboseness level
    #[argh(option, short = 'v', from_str_fn(verbosity_level), default = "slog::Level::Info")]
    verbosity: slog::Level,

    /// specify the configuration file to use. Default to "/etc/updatehub.conf"
    #[argh(option, short = 'c', default = "PathBuf::from(\"/etc/updatehub.conf\")")]
    conf_file: PathBuf,
}

fn verbosity_level(value: &str) -> Result<slog::Level, String> {
    use std::str::FromStr;
    slog::Level::from_str(value).map_err(|_| format!("failed to parse verbosity level: {}", value))
}

async fn server_main(cmd: ServerOptions) -> updatehub::Result<()> {
    updatehub::logger::init(cmd.verbosity);
    info!("starting UpdateHub Agent {}", updatehub::version());

    let settings = updatehub::Settings::load(&cmd.conf_file)?;

    updatehub::run(settings).await?;

    Ok(())
}

async fn client_main(cmd: ClientCommands) -> updatehub::Result<()> {
    let client = sdk::Client::new("localhost:8080");

    match cmd {
        ClientCommands::Info(_) => println!("{:#?}", client.info().await),
        ClientCommands::Log(_) => println!("{:#?}", client.log().await),
        ClientCommands::Probe(Probe { server }) => println!("{:#?}", client.probe(server).await),
        ClientCommands::AbortDownload(_) => println!("{:#?}", client.abort_download().await),
        ClientCommands::LocalInstall(LocalInstall { file }) => {
            let file =
                if file.is_absolute() { file } else { std::env::current_dir().unwrap().join(file) };
            println!("{:#?}", client.local_install(&file).await)
        }
        ClientCommands::RemoteInstall(RemoteInstall { url }) => {
            println!("{:#?}", client.remote_install(&url).await)
        }
    }

    Ok(())
}

#[actix_rt::main]
async fn main() {
    let cmd: TopLevel = argh::from_env();

    let res = match cmd.entry_point {
        EntryPoints::Client(client) => client_main(client.commands).await,
        EntryPoints::Server(cmd) => server_main(cmd).await,
    };

    if let Err(e) = res {
        eprintln!("{}", e);
        std::process::exit(1);
    }
}
<|endoftext|>


<commit_before>// Generic ARMv7-A target for bare-metal code - floating point disabled
//
// This is basically the `armv7-unknown-linux-gnueabi` target with some changes
// (listed below) to bring it closer to the bare-metal `thumb` & `aarch64`
// targets:
//
// - `TargetOptions.features`: added `+strict-align`. rationale: unaligned
// memory access is disabled on boot on these cores
// - linker changed to LLD. rationale: C is not strictly needed to build
// bare-metal binaries (the `gcc` linker has the advantage that it knows where C
// libraries and crt*.o are but it's not much of an advantage here); LLD is also
// faster
// - `os` set to `none`. rationale: matches `thumb` targets
// - `target_{env,vendor}` set to an empty string. rationale: matches `thumb`
// targets
// - `panic_strategy` set to `abort`. rationale: matches `thumb` targets
// - `relocation-model` set to `static`; also no PIE, no relro and no dynamic
// linking. rationale: matches `thumb` targets

use super::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, Target, TargetOptions};

pub fn target() -> Target {
    let opts = TargetOptions {
        vendor: String::new(),
        linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
        linker: Some("rust-lld".to_owned()),
        features: "+v7,+thumb2,+soft-float,-neon,+strict-align".to_string(),
        executables: true,
        relocation_model: RelocModel::Static,
        disable_redzone: true,
        max_atomic_width: Some(64),
        panic_strategy: PanicStrategy::Abort,
        unsupported_abis: super::arm_base::unsupported_abis(),
        emit_debug_gdb_scripts: false,
        ..Default::default()
    };
    Target {
        llvm_target: "armv7a-none-eabi".to_string(),
        pointer_width: 32,
        data_layout: "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64".to_string(),
        arch: "arm".to_string(),
        options: opts,
    }
}
<commit_msg>Address review comments<commit_after>// Generic ARMv7-A target for bare-metal code - floating point disabled
//
// This is basically the `armv7-unknown-linux-gnueabi` target with some changes
// (listed below) to bring it closer to the bare-metal `thumb` & `aarch64`
// targets:
//
// - `TargetOptions.features`: added `+strict-align`. rationale: unaligned
// memory access is disabled on boot on these cores
// - linker changed to LLD. rationale: C is not strictly needed to build
// bare-metal binaries (the `gcc` linker has the advantage that it knows where C
// libraries and crt*.o are but it's not much of an advantage here); LLD is also
// faster
// - `os` set to `none`. rationale: matches `thumb` targets
// - `env` and `vendor` are set to an empty string. rationale: matches `thumb`
// targets
// - `panic_strategy` set to `abort`. rationale: matches `thumb` targets
// - `relocation-model` set to `static`; also no PIE, no relro and no dynamic
// linking. rationale: matches `thumb` targets

use super::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, Target, TargetOptions};

pub fn target() -> Target {
    let opts = TargetOptions {
        vendor: String::new(),
        linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
        linker: Some("rust-lld".to_owned()),
        features: "+v7,+thumb2,+soft-float,-neon,+strict-align".to_string(),
        executables: true,
        relocation_model: RelocModel::Static,
        disable_redzone: true,
        max_atomic_width: Some(64),
        panic_strategy: PanicStrategy::Abort,
        unsupported_abis: super::arm_base::unsupported_abis(),
        emit_debug_gdb_scripts: false,
        ..Default::default()
    };
    Target {
        llvm_target: "armv7a-none-eabi".to_string(),
        pointer_width: 32,
        data_layout: "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64".to_string(),
        arch: "arm".to_string(),
        options: opts,
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use std::iter::FromIterator;
use std::str::FromStr;

/// Read input line by line and try to parse it into some collection.
pub fn from_lines<I, T, E>(input: &mut dyn Read) -> T
where
    I: FromStr<Err = E>,
    E: Debug,
    T: FromIterator<I>,
{
    let reader = BufReader::new(input);

    reader
        .lines()
        .map(|line| line.unwrap().parse::<I>().unwrap())
        .collect()
}

/// Parse the entire input into a single variable
pub fn read_single_input<T>(input: &mut dyn Read) -> T
where
    T: FromStr,
    <T as FromStr>::Err: Debug,
{
    let mut buf = String::new();
    input.read_to_string(&mut buf).unwrap();

    buf.trim().parse().unwrap()
}

pub fn read_char_grid(input: &mut dyn Read) -> Vec<Vec<u8>> {
    let mut reader = BufReader::new(input);
    let mut buffer = Vec::new();

    let mut grid = Vec::new();

    while let Ok(read) = reader.read_until(b'\n', &mut buffer) {
        if read == 0 {
            break;
        }

        let line: &[u8] = if let Some(&b'\n') = buffer.last() {
            &buffer[..(buffer.len() - 1)]
        } else {
            &buffer[..]
        };

        grid.push(line.to_owned());
        buffer.clear();
    }

    grid
}

/// An interface to count elements in particular categories.
pub trait GroupingCount {
    /// The type of the categories under inspection
    type Type;

    /// Count the occurrence of all possible values.
    ///
    /// This method will return a map from a value to its occurrence rate.
    fn grouping_count(&mut self) -> HashMap<Self::Type, usize>;
}

impl<T> GroupingCount for T
where
    T: Iterator,
    T::Item: Eq + Hash,
{
    type Type = T::Item;

    fn grouping_count(&mut self) -> HashMap<Self::Type, usize> {
        let mut counts = HashMap::new();

        for element in self {
            *counts.entry(element).or_insert(0) += 1;
        }

        counts
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_grouping_count() {
        let result = [1, 1, 2, 2, 3, 1].iter().grouping_count();
        assert_eq!(3, result[&1]);
        assert_eq!(2, result[&2]);
        assert_eq!(1, result[&3]);
    }
}
<commit_msg>Simplify read_char_grid<commit_after>use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Read;
use std::iter::FromIterator;
use std::str::FromStr;

/// Read input line by line and try to parse it into some collection.
pub fn from_lines<I, T, E>(input: &mut dyn Read) -> T
where
    I: FromStr<Err = E>,
    E: Debug,
    T: FromIterator<I>,
{
    let reader = BufReader::new(input);

    reader
        .lines()
        .map(|line| line.unwrap().parse::<I>().unwrap())
        .collect()
}

/// Parse the entire input into a single variable
pub fn read_single_input<T>(input: &mut dyn Read) -> T
where
    T: FromStr,
    <T as FromStr>::Err: Debug,
{
    let mut buf = String::new();
    input.read_to_string(&mut buf).unwrap();

    buf.trim().parse().unwrap()
}

pub fn read_char_grid(input: &mut dyn Read) -> Vec<Vec<u8>> {
    BufReader::new(input)
        .lines()
        // filter_map avoids an expensive unwrap and we know our input is valid ascii
        .filter_map(|s| s.ok().map(String::into_bytes))
        .collect()
}

/// An interface to count elements in particular categories.
pub trait GroupingCount {
    /// The type of the categories under inspection
    type Type;

    /// Count the occurrence of all possible values.
    ///
    /// This method will return a map from a value to its occurrence rate.
    fn grouping_count(&mut self) -> HashMap<Self::Type, usize>;
}

impl<T> GroupingCount for T
where
    T: Iterator,
    T::Item: Eq + Hash,
{
    type Type = T::Item;

    fn grouping_count(&mut self) -> HashMap<Self::Type, usize> {
        let mut counts = HashMap::new();

        for element in self {
            *counts.entry(element).or_insert(0) += 1;
        }

        counts
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_grouping_count() {
        let result = [1, 1, 2, 2, 3, 1].iter().grouping_count();
        assert_eq!(3, result[&1]);
        assert_eq!(2, result[&2]);
        assert_eq!(1, result[&3]);
    }
}
<|endoftext|>


<commit_before>use codespan::{CodeMap, LineIndex, LineNumber};
use std::{fmt, io};
use termcolor::{Color, ColorSpec, WriteColor};

use crate::{Diagnostic, LabelStyle};

struct Pad<T>(T, usize);

impl<T: fmt::Display> fmt::Display for Pad<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for _ in 0..(self.1) {
            self.0.fmt(f)?;
        }
        Ok(())
    }
}

// Blue is really difficult to see on the standard windows command line
// FIXME: Make colors configurable
#[cfg(windows)]
const BLUE: Color = Color::Cyan;
#[cfg(not(windows))]
const BLUE: Color = Color::Blue;

pub fn emit<W, S>(mut writer: W, codemap: &CodeMap<S>, diagnostic: &Diagnostic) -> io::Result<()>
where
    W: WriteColor,
    S: AsRef<str>,
{
    let line_location_color = ColorSpec::new().set_fg(Some(BLUE)).clone();
    let diagnostic_color = ColorSpec::new()
        .set_fg(Some(diagnostic.severity.color()))
        .clone();

    let highlight_color = ColorSpec::new().set_bold(true).set_intense(true).clone();

    writer.set_color(
        &highlight_color
            .clone()
            .set_fg(Some(diagnostic.severity.color())),
    )?;
    write!(writer, "{}", diagnostic.severity)?;

    if let Some(ref code) = diagnostic.code {
        write!(writer, "[{}]", code)?;
    }

    writer.set_color(&highlight_color)?;
    writeln!(writer, ": {}", diagnostic.message)?;
    writer.reset()?;

    for label in &diagnostic.labels {
        match codemap.find_file(label.span.start()) {
            None => {
                if !label.message.is_empty() {
                    writeln!(writer, "- {}", label.message)?
                }
            },
            Some(file) => {
                let (start_line, column) =
                    file.location(label.span.start()).expect("location_start");
                let (end_line, _) = file.location(label.span.end()).expect("location_end");

                writeln!(
                    writer,
                    "- {file}:{line}:{column}",
                    file = file.name(),
                    line = start_line.number(),
                    column = column.number(),
                )?;

                let start_line_span = file.line_span(start_line).expect("line_span");
                let end_line_span = file.line_span(end_line).expect("line_span");
                let line_location_width = end_line.number().to_string().len();
                let line_location_prefix = format!("{} | ", Pad(' ', line_location_width));

                let label_color = match label.style {
                    LabelStyle::Primary => diagnostic_color.clone(),
                    LabelStyle::Secondary => ColorSpec::new().set_fg(Some(BLUE)).clone(),
                };

                // Write prefix to marked section
                //
                writer.set_color(&line_location_color)?;
                let line_string = start_line.number().to_string();
                writeln!(writer, "{} |", Pad(' ', line_location_width))?;
                write!(writer, "{} | ", line_string)?;

                let prefix = file
                    .src_slice(start_line_span.with_end(label.span.start()))
                    .expect("prefix");
                writer.reset()?;
                write!(writer, "{}", prefix)?;

                // Write marked section
                //
                let LineNumber(start) = start_line.number();
                let LineNumber(end) = end_line.number();

                let mark_len = if start == end {
                    let marked = file.src_slice(label.span).expect("marked");
                    writer.set_color(&label_color)?;
                    write!(writer, "{}", marked)?;
                    marked.len()
                } else {
                    let marked = file
                        .src_slice(start_line_span.with_start(label.span.start()))
                        .expect("start_of_marked");
                    writer.set_color(&label_color)?;
                    write!(writer, "{}", marked)?;

                    for i in start..(end - 1) {
                        writer.set_color(&line_location_color)?;
                        write!(writer, "{} | ", (i + 1).to_string())?;

                        let line_span = file.line_span(LineIndex(i)).expect("marked_line_span");
                        let line = file
                            .src_slice(line_span)
                            .expect("line")
                            .trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
                        writer.set_color(&label_color)?;
                        writeln!(writer, "{}", line)?;
                    }

                    writer.set_color(&line_location_color)?;
                    write!(writer, "{} | ", end)?;
                    let line = file
                        .src_slice(end_line_span.with_end(label.span.end()))
                        .expect("line");
                    writer.set_color(&label_color)?;
                    write!(writer, "{}", line)?;
                    line.len()
                };

                // Write suffix to marked section
                //
                let suffix = file
                    .src_slice(end_line_span.with_start(label.span.end()))
                    .expect("suffix")
                    .trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
                writer.reset()?;
                writeln!(writer, "{}", suffix)?;

                // Write mark and label

                let mark = match label.style {
                    LabelStyle::Primary => '^',
                    LabelStyle::Secondary => '-',
                };

                writer.set_color(&line_location_color)?;
                write!(writer, "{}", line_location_prefix)?;
                writer.reset()?;

                writer.set_color(&label_color)?;
                write!(writer, "{}{}", Pad(' ', prefix.len()), Pad(mark, mark_len))?;
                writer.reset()?;

                if !label.message.is_empty() {
                    writer.set_color(&label_color)?;
                    writeln!(writer, " {}", label.message)?;
                    writer.reset()?;
                }
                writer.set_color(&line_location_color)?;
                writeln!(writer, "{} |", Pad(' ', line_location_width))?;
                writer.reset()?;
            },
        }
    }
    Ok(())
}
<commit_msg>Remove Pad type in favour of fmt-native padding<commit_after>use codespan::{CodeMap, LineIndex, LineNumber};
use std::io;
use termcolor::{Color, ColorSpec, WriteColor};

use crate::{Diagnostic, LabelStyle};

// Blue is really difficult to see on the standard windows command line
// FIXME: Make colors configurable
#[cfg(windows)]
const BLUE: Color = Color::Cyan;
#[cfg(not(windows))]
const BLUE: Color = Color::Blue;

pub fn emit<W, S>(mut writer: W, codemap: &CodeMap<S>, diagnostic: &Diagnostic) -> io::Result<()>
where
    W: WriteColor,
    S: AsRef<str>,
{
    let line_location_color = ColorSpec::new().set_fg(Some(BLUE)).clone();
    let diagnostic_color = ColorSpec::new()
        .set_fg(Some(diagnostic.severity.color()))
        .clone();

    let highlight_color = ColorSpec::new().set_bold(true).set_intense(true).clone();

    writer.set_color(
        &highlight_color
            .clone()
            .set_fg(Some(diagnostic.severity.color())),
    )?;
    write!(writer, "{}", diagnostic.severity)?;

    if let Some(ref code) = diagnostic.code {
        write!(writer, "[{}]", code)?;
    }

    writer.set_color(&highlight_color)?;
    writeln!(writer, ": {}", diagnostic.message)?;
    writer.reset()?;

    for label in &diagnostic.labels {
        match codemap.find_file(label.span.start()) {
            None => {
                if !label.message.is_empty() {
                    writeln!(writer, "- {}", label.message)?
                }
            },
            Some(file) => {
                let (start_line, column) =
                    file.location(label.span.start()).expect("location_start");
                let (end_line, _) = file.location(label.span.end()).expect("location_end");

                writeln!(
                    writer,
                    "- {file}:{line}:{column}",
                    file = file.name(),
                    line = start_line.number(),
                    column = column.number(),
                )?;

                let start_line_span = file.line_span(start_line).expect("line_span");
                let end_line_span = file.line_span(end_line).expect("line_span");
                let line_location_width = end_line.number().to_string().len();

                let label_color = match label.style {
                    LabelStyle::Primary => diagnostic_color.clone(),
                    LabelStyle::Secondary => ColorSpec::new().set_fg(Some(BLUE)).clone(),
                };

                // Write prefix to marked section
                //
                writer.set_color(&line_location_color)?;
                let line_string = start_line.number().to_string();
                writeln!(writer, "{: <width$} | ", "", width = line_location_width)?;
                write!(writer, "{} | ", line_string)?;

                let prefix = file
                    .src_slice(start_line_span.with_end(label.span.start()))
                    .expect("prefix");
                writer.reset()?;
                write!(writer, "{}", prefix)?;

                // Write marked section
                //
                let LineNumber(start) = start_line.number();
                let LineNumber(end) = end_line.number();

                let mark_len = if start == end {
                    let marked = file.src_slice(label.span).expect("marked");
                    writer.set_color(&label_color)?;
                    write!(writer, "{}", marked)?;
                    marked.len()
                } else {
                    let marked = file
                        .src_slice(start_line_span.with_start(label.span.start()))
                        .expect("start_of_marked");
                    writer.set_color(&label_color)?;
                    write!(writer, "{}", marked)?;

                    for i in start..(end - 1) {
                        writer.set_color(&line_location_color)?;
                        write!(writer, "{} | ", (i + 1).to_string())?;

                        let line_span = file.line_span(LineIndex(i)).expect("marked_line_span");
                        let line = file
                            .src_slice(line_span)
                            .expect("line")
                            .trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
                        writer.set_color(&label_color)?;
                        writeln!(writer, "{}", line)?;
                    }

                    writer.set_color(&line_location_color)?;
                    write!(writer, "{} | ", end)?;
                    let line = file
                        .src_slice(end_line_span.with_end(label.span.end()))
                        .expect("line");
                    writer.set_color(&label_color)?;
                    write!(writer, "{}", line)?;
                    line.len()
                };

                // Write suffix to marked section
                //
                let suffix = file
                    .src_slice(end_line_span.with_start(label.span.end()))
                    .expect("suffix")
                    .trim_end_matches(|ch: char| ch == '\r' || ch == '\n');
                writer.reset()?;
                writeln!(writer, "{}", suffix)?;

                // Write mark and label

                let mark = match label.style {
                    LabelStyle::Primary => '^',
                    LabelStyle::Secondary => '-',
                };

                writer.set_color(&line_location_color)?;
                write!(writer, "{: <width$} | ", "", width = line_location_width)?;
                writer.reset()?;

                writer.set_color(&label_color)?;
                write!(writer, "{: <width$}", "", width = prefix.len())?;
                for _ in 0..mark_len {
                    write!(writer, "{}", mark)?;
                }
                writer.reset()?;

                if !label.message.is_empty() {
                    writer.set_color(&label_color)?;
                    writeln!(writer, " {}", label.message)?;
                    writer.reset()?;
                }
                writer.set_color(&line_location_color)?;
                writeln!(writer, "{: <width$} | ", "", width = line_location_width)?;
                writer.reset()?;
            },
        }
    }
    Ok(())
}
<|endoftext|>


<commit_before>//! FIXME: write short doc here

use hir::{Semantics, SemanticsScope};
use ra_db::SourceDatabase;
use ra_ide_db::RootDatabase;
use ra_syntax::{
    algo::{find_covering_element, find_node_at_offset},
    ast, AstNode,
    SyntaxKind::*,
    SyntaxNode, SyntaxToken, TextRange, TextUnit,
};
use ra_text_edit::AtomTextEdit;

use crate::FilePosition;

/// `CompletionContext` is created early during completion to figure out, where
/// exactly is the cursor, syntax-wise.
#[derive(Debug)]
pub(crate) struct CompletionContext<'a> {
    pub(super) sema: Semantics<'a, RootDatabase>,
    pub(super) db: &'a RootDatabase,
    pub(super) offset: TextUnit,
    /// The token before the cursor, in the original file.
    pub(super) original_token: SyntaxToken,
    /// The token before the cursor, in the macro-expanded file.
    pub(super) token: SyntaxToken,
    pub(super) module: Option<hir::Module>,
    pub(super) name_ref_syntax: Option<ast::NameRef>,
    pub(super) function_syntax: Option<ast::FnDef>,
    pub(super) use_item_syntax: Option<ast::UseItem>,
    pub(super) record_lit_syntax: Option<ast::RecordLit>,
    pub(super) record_lit_pat: Option<ast::RecordPat>,
    pub(super) impl_def: Option<ast::ImplDef>,
    pub(super) is_param: bool,
    /// If a name-binding or reference to a const in a pattern.
    /// Irrefutable patterns (like let) are excluded.
    pub(super) is_pat_binding: bool,
    /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
    pub(super) is_trivial_path: bool,
    /// If not a trivial path, the prefix (qualifier).
    pub(super) path_prefix: Option<hir::Path>,
    pub(super) after_if: bool,
    /// `true` if we are a statement or a last expr in the block.
    pub(super) can_be_stmt: bool,
    /// Something is typed at the "top" level, in module or impl/trait.
    pub(super) is_new_item: bool,
    /// The receiver if this is a field or method access, i.e. writing something.<|>
    pub(super) dot_receiver: Option<ast::Expr>,
    pub(super) dot_receiver_is_ambiguous_float_literal: bool,
    /// If this is a call (method or function) in particular, i.e. the () are already there.
    pub(super) is_call: bool,
    pub(super) is_path_type: bool,
    pub(super) has_type_args: bool,
}

impl<'a> CompletionContext<'a> {
    pub(super) fn new(
        db: &'a RootDatabase,
        position: FilePosition,
    ) -> Option<CompletionContext<'a>> {
        let sema = Semantics::new(db);

        let original_file = sema.parse(position.file_id);

        // Insert a fake ident to get a valid parse tree. We will use this file
        // to determine context, though the original_file will be used for
        // actual completion.
        let file_with_fake_ident = {
            let parse = db.parse(position.file_id);
            let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
            parse.reparse(&edit).tree()
        };
        let fake_ident_token =
            file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap();

        // TODO: shouldn't this take the position into account? (in case we're inside a mod {})
        let module = sema.to_module_def(position.file_id);
        let original_token =
            original_file.syntax().token_at_offset(position.offset).left_biased()?;
        let token = sema.descend_into_macros(original_token.clone());
        let mut ctx = CompletionContext {
            sema,
            db,
            original_token,
            token,
            offset: position.offset,
            module,
            name_ref_syntax: None,
            function_syntax: None,
            use_item_syntax: None,
            record_lit_syntax: None,
            record_lit_pat: None,
            impl_def: None,
            is_param: false,
            is_pat_binding: false,
            is_trivial_path: false,
            path_prefix: None,
            after_if: false,
            can_be_stmt: false,
            is_new_item: false,
            dot_receiver: None,
            is_call: false,
            is_path_type: false,
            has_type_args: false,
            dot_receiver_is_ambiguous_float_literal: false,
        };

        let mut original_file = original_file.syntax().clone();
        let mut hypothetical_file = file_with_fake_ident.syntax().clone();
        let mut offset = position.offset;
        let mut fake_ident_token = fake_ident_token;

        // Are we inside a macro call?
        while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
            find_node_at_offset::<ast::MacroCall>(&original_file, offset),
            find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset),
        ) {
            if let (Some(actual_expansion), Some(hypothetical_expansion)) = (
                ctx.sema.expand(&actual_macro_call),
                ctx.sema.expand_hypothetical(
                    &actual_macro_call,
                    &macro_call_with_fake_ident,
                    fake_ident_token,
                ),
            ) {
                // TODO check that the expansions 'look the same' up to the inserted token?
                original_file = actual_expansion;
                hypothetical_file = hypothetical_expansion.0;
                fake_ident_token = hypothetical_expansion.1;
                offset = fake_ident_token.text_range().start();
            } else {
                break;
            }
        }

        ctx.fill(&original_file, hypothetical_file, offset);
        Some(ctx)
    }

    // The range of the identifier that is being completed.
    pub(crate) fn source_range(&self) -> TextRange {
        // check kind of macro-expanded token, but use range of original token
        match self.token.kind() {
            // workaroud when completion is triggered by trigger characters.
            IDENT => self.original_token.text_range(),
            _ => TextRange::offset_len(self.offset, 0.into()),
        }
    }

    pub(crate) fn scope(&self) -> SemanticsScope<'_, RootDatabase> {
        self.sema.scope_at_offset(&self.token.parent(), self.offset)
    }

    fn fill(
        &mut self,
        original_file: &SyntaxNode,
        file_with_fake_ident: SyntaxNode,
        offset: TextUnit,
    ) {
        // First, let's try to complete a reference to some declaration.
        if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
            // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
            // See RFC#1685.
            if is_node::<ast::Param>(name_ref.syntax()) {
                self.is_param = true;
                return;
            }
            self.classify_name_ref(original_file, name_ref, offset);
        }

        // Otherwise, see if this is a declaration. We can use heuristics to
        // suggest declaration names, see `CompletionKind::Magic`.
        if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) {
            if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) {
                let parent = bind_pat.syntax().parent();
                if parent.clone().and_then(ast::MatchArm::cast).is_some()
                    || parent.and_then(ast::Condition::cast).is_some()
                {
                    self.is_pat_binding = true;
                }
            }
            if is_node::<ast::Param>(name.syntax()) {
                self.is_param = true;
                return;
            }
            if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() {
                self.record_lit_pat =
                    self.sema.find_node_at_offset_with_macros(&original_file, offset);
            }
        }
    }

    fn classify_name_ref(
        &mut self,
        original_file: &SyntaxNode,
        name_ref: ast::NameRef,
        offset: TextUnit,
    ) {
        self.name_ref_syntax =
            find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
        let name_range = name_ref.syntax().text_range();
        if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() {
            self.record_lit_syntax =
                self.sema.find_node_at_offset_with_macros(&original_file, offset);
        }

        self.impl_def = self
            .sema
            .ancestors_with_macros(self.token.parent())
            .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
            .find_map(ast::ImplDef::cast);

        let top_node = name_ref
            .syntax()
            .ancestors()
            .take_while(|it| it.text_range() == name_range)
            .last()
            .unwrap();

        match top_node.parent().map(|it| it.kind()) {
            Some(SOURCE_FILE) | Some(ITEM_LIST) => {
                self.is_new_item = true;
                return;
            }
            _ => (),
        }

        self.use_item_syntax =
            self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast);

        self.function_syntax = self
            .sema
            .ancestors_with_macros(self.token.parent())
            .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
            .find_map(ast::FnDef::cast);

        let parent = match name_ref.syntax().parent() {
            Some(it) => it,
            None => return,
        };

        if let Some(segment) = ast::PathSegment::cast(parent.clone()) {
            let path = segment.parent_path();
            self.is_call = path
                .syntax()
                .parent()
                .and_then(ast::PathExpr::cast)
                .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
                .is_some();

            self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
            self.has_type_args = segment.type_arg_list().is_some();

            if let Some(path) = hir::Path::from_ast(path.clone()) {
                if let Some(path_prefix) = path.qualifier() {
                    self.path_prefix = Some(path_prefix);
                    return;
                }
            }

            if path.qualifier().is_none() {
                self.is_trivial_path = true;

                // Find either enclosing expr statement (thing with `;`) or a
                // block. If block, check that we are the last expr.
                self.can_be_stmt = name_ref
                    .syntax()
                    .ancestors()
                    .find_map(|node| {
                        if let Some(stmt) = ast::ExprStmt::cast(node.clone()) {
                            return Some(
                                stmt.syntax().text_range() == name_ref.syntax().text_range(),
                            );
                        }
                        if let Some(block) = ast::Block::cast(node) {
                            return Some(
                                block.expr().map(|e| e.syntax().text_range())
                                    == Some(name_ref.syntax().text_range()),
                            );
                        }
                        None
                    })
                    .unwrap_or(false);

                if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
                    if let Some(if_expr) =
                        self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
                    {
                        if if_expr.syntax().text_range().end()
                            < name_ref.syntax().text_range().start()
                        {
                            self.after_if = true;
                        }
                    }
                }
            }
        }
        if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
            // The receiver comes before the point of insertion of the fake
            // ident, so it should have the same range in the non-modified file
            self.dot_receiver = field_expr
                .expr()
                .map(|e| e.syntax().text_range())
                .and_then(|r| find_node_with_range(original_file, r));
            self.dot_receiver_is_ambiguous_float_literal =
                if let Some(ast::Expr::Literal(l)) = &self.dot_receiver {
                    match l.kind() {
                        ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'),
                        _ => false,
                    }
                } else {
                    false
                }
        }
        if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
            // As above
            self.dot_receiver = method_call_expr
                .expr()
                .map(|e| e.syntax().text_range())
                .and_then(|r| find_node_with_range(original_file, r));
            self.is_call = true;
        }
    }
}

fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
    find_covering_element(syntax, range).ancestors().find_map(N::cast)
}

fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
    match node.ancestors().find_map(N::cast) {
        None => false,
        Some(n) => n.syntax().text_range() == node.text_range(),
    }
}
<commit_msg>Add some sanity checks<commit_after>//! FIXME: write short doc here

use hir::{Semantics, SemanticsScope};
use ra_db::SourceDatabase;
use ra_ide_db::RootDatabase;
use ra_syntax::{
    algo::{find_covering_element, find_node_at_offset},
    ast, AstNode,
    SyntaxKind::*,
    SyntaxNode, SyntaxToken, TextRange, TextUnit,
};
use ra_text_edit::AtomTextEdit;

use crate::FilePosition;

/// `CompletionContext` is created early during completion to figure out, where
/// exactly is the cursor, syntax-wise.
#[derive(Debug)]
pub(crate) struct CompletionContext<'a> {
    pub(super) sema: Semantics<'a, RootDatabase>,
    pub(super) db: &'a RootDatabase,
    pub(super) offset: TextUnit,
    /// The token before the cursor, in the original file.
    pub(super) original_token: SyntaxToken,
    /// The token before the cursor, in the macro-expanded file.
    pub(super) token: SyntaxToken,
    pub(super) module: Option<hir::Module>,
    pub(super) name_ref_syntax: Option<ast::NameRef>,
    pub(super) function_syntax: Option<ast::FnDef>,
    pub(super) use_item_syntax: Option<ast::UseItem>,
    pub(super) record_lit_syntax: Option<ast::RecordLit>,
    pub(super) record_lit_pat: Option<ast::RecordPat>,
    pub(super) impl_def: Option<ast::ImplDef>,
    pub(super) is_param: bool,
    /// If a name-binding or reference to a const in a pattern.
    /// Irrefutable patterns (like let) are excluded.
    pub(super) is_pat_binding: bool,
    /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
    pub(super) is_trivial_path: bool,
    /// If not a trivial path, the prefix (qualifier).
    pub(super) path_prefix: Option<hir::Path>,
    pub(super) after_if: bool,
    /// `true` if we are a statement or a last expr in the block.
    pub(super) can_be_stmt: bool,
    /// Something is typed at the "top" level, in module or impl/trait.
    pub(super) is_new_item: bool,
    /// The receiver if this is a field or method access, i.e. writing something.<|>
    pub(super) dot_receiver: Option<ast::Expr>,
    pub(super) dot_receiver_is_ambiguous_float_literal: bool,
    /// If this is a call (method or function) in particular, i.e. the () are already there.
    pub(super) is_call: bool,
    pub(super) is_path_type: bool,
    pub(super) has_type_args: bool,
}

impl<'a> CompletionContext<'a> {
    pub(super) fn new(
        db: &'a RootDatabase,
        position: FilePosition,
    ) -> Option<CompletionContext<'a>> {
        let sema = Semantics::new(db);

        let original_file = sema.parse(position.file_id);

        // Insert a fake ident to get a valid parse tree. We will use this file
        // to determine context, though the original_file will be used for
        // actual completion.
        let file_with_fake_ident = {
            let parse = db.parse(position.file_id);
            let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
            parse.reparse(&edit).tree()
        };
        let fake_ident_token =
            file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap();

        // TODO: shouldn't this take the position into account? (in case we're inside a mod {})
        let module = sema.to_module_def(position.file_id);
        let original_token =
            original_file.syntax().token_at_offset(position.offset).left_biased()?;
        let token = sema.descend_into_macros(original_token.clone());
        let mut ctx = CompletionContext {
            sema,
            db,
            original_token,
            token,
            offset: position.offset,
            module,
            name_ref_syntax: None,
            function_syntax: None,
            use_item_syntax: None,
            record_lit_syntax: None,
            record_lit_pat: None,
            impl_def: None,
            is_param: false,
            is_pat_binding: false,
            is_trivial_path: false,
            path_prefix: None,
            after_if: false,
            can_be_stmt: false,
            is_new_item: false,
            dot_receiver: None,
            is_call: false,
            is_path_type: false,
            has_type_args: false,
            dot_receiver_is_ambiguous_float_literal: false,
        };

        let mut original_file = original_file.syntax().clone();
        let mut hypothetical_file = file_with_fake_ident.syntax().clone();
        let mut offset = position.offset;
        let mut fake_ident_token = fake_ident_token;

        // Are we inside a macro call?
        while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
            find_node_at_offset::<ast::MacroCall>(&original_file, offset),
            find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset),
        ) {
            if actual_macro_call.path().as_ref().map(|s| s.syntax().text())
                != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text())
            {
                break;
            }
            if let (Some(actual_expansion), Some(hypothetical_expansion)) = (
                ctx.sema.expand(&actual_macro_call),
                ctx.sema.expand_hypothetical(
                    &actual_macro_call,
                    &macro_call_with_fake_ident,
                    fake_ident_token,
                ),
            ) {
                let new_offset = hypothetical_expansion.1.text_range().start();
                if new_offset >= actual_expansion.text_range().end() {
                    break;
                }
                // TODO check that the expansions 'look the same' up to the inserted token?
                original_file = actual_expansion;
                hypothetical_file = hypothetical_expansion.0;
                fake_ident_token = hypothetical_expansion.1;
                offset = new_offset;
            } else {
                break;
            }
        }

        ctx.fill(&original_file, hypothetical_file, offset);
        Some(ctx)
    }

    // The range of the identifier that is being completed.
    pub(crate) fn source_range(&self) -> TextRange {
        // check kind of macro-expanded token, but use range of original token
        match self.token.kind() {
            // workaroud when completion is triggered by trigger characters.
            IDENT => self.original_token.text_range(),
            _ => TextRange::offset_len(self.offset, 0.into()),
        }
    }

    pub(crate) fn scope(&self) -> SemanticsScope<'_, RootDatabase> {
        self.sema.scope_at_offset(&self.token.parent(), self.offset)
    }

    fn fill(
        &mut self,
        original_file: &SyntaxNode,
        file_with_fake_ident: SyntaxNode,
        offset: TextUnit,
    ) {
        // First, let's try to complete a reference to some declaration.
        if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
            // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
            // See RFC#1685.
            if is_node::<ast::Param>(name_ref.syntax()) {
                self.is_param = true;
                return;
            }
            self.classify_name_ref(original_file, name_ref, offset);
        }

        // Otherwise, see if this is a declaration. We can use heuristics to
        // suggest declaration names, see `CompletionKind::Magic`.
        if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) {
            if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) {
                let parent = bind_pat.syntax().parent();
                if parent.clone().and_then(ast::MatchArm::cast).is_some()
                    || parent.and_then(ast::Condition::cast).is_some()
                {
                    self.is_pat_binding = true;
                }
            }
            if is_node::<ast::Param>(name.syntax()) {
                self.is_param = true;
                return;
            }
            if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() {
                self.record_lit_pat =
                    self.sema.find_node_at_offset_with_macros(&original_file, offset);
            }
        }
    }

    fn classify_name_ref(
        &mut self,
        original_file: &SyntaxNode,
        name_ref: ast::NameRef,
        offset: TextUnit,
    ) {
        self.name_ref_syntax =
            find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
        let name_range = name_ref.syntax().text_range();
        if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() {
            self.record_lit_syntax =
                self.sema.find_node_at_offset_with_macros(&original_file, offset);
        }

        self.impl_def = self
            .sema
            .ancestors_with_macros(self.token.parent())
            .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
            .find_map(ast::ImplDef::cast);

        let top_node = name_ref
            .syntax()
            .ancestors()
            .take_while(|it| it.text_range() == name_range)
            .last()
            .unwrap();

        match top_node.parent().map(|it| it.kind()) {
            Some(SOURCE_FILE) | Some(ITEM_LIST) => {
                self.is_new_item = true;
                return;
            }
            _ => (),
        }

        self.use_item_syntax =
            self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast);

        self.function_syntax = self
            .sema
            .ancestors_with_macros(self.token.parent())
            .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
            .find_map(ast::FnDef::cast);

        let parent = match name_ref.syntax().parent() {
            Some(it) => it,
            None => return,
        };

        if let Some(segment) = ast::PathSegment::cast(parent.clone()) {
            let path = segment.parent_path();
            self.is_call = path
                .syntax()
                .parent()
                .and_then(ast::PathExpr::cast)
                .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
                .is_some();

            self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
            self.has_type_args = segment.type_arg_list().is_some();

            if let Some(path) = hir::Path::from_ast(path.clone()) {
                if let Some(path_prefix) = path.qualifier() {
                    self.path_prefix = Some(path_prefix);
                    return;
                }
            }

            if path.qualifier().is_none() {
                self.is_trivial_path = true;

                // Find either enclosing expr statement (thing with `;`) or a
                // block. If block, check that we are the last expr.
                self.can_be_stmt = name_ref
                    .syntax()
                    .ancestors()
                    .find_map(|node| {
                        if let Some(stmt) = ast::ExprStmt::cast(node.clone()) {
                            return Some(
                                stmt.syntax().text_range() == name_ref.syntax().text_range(),
                            );
                        }
                        if let Some(block) = ast::Block::cast(node) {
                            return Some(
                                block.expr().map(|e| e.syntax().text_range())
                                    == Some(name_ref.syntax().text_range()),
                            );
                        }
                        None
                    })
                    .unwrap_or(false);

                if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
                    if let Some(if_expr) =
                        self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
                    {
                        if if_expr.syntax().text_range().end()
                            < name_ref.syntax().text_range().start()
                        {
                            self.after_if = true;
                        }
                    }
                }
            }
        }
        if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
            // The receiver comes before the point of insertion of the fake
            // ident, so it should have the same range in the non-modified file
            self.dot_receiver = field_expr
                .expr()
                .map(|e| e.syntax().text_range())
                .and_then(|r| find_node_with_range(original_file, r));
            self.dot_receiver_is_ambiguous_float_literal =
                if let Some(ast::Expr::Literal(l)) = &self.dot_receiver {
                    match l.kind() {
                        ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'),
                        _ => false,
                    }
                } else {
                    false
                }
        }
        if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
            // As above
            self.dot_receiver = method_call_expr
                .expr()
                .map(|e| e.syntax().text_range())
                .and_then(|r| find_node_with_range(original_file, r));
            self.is_call = true;
        }
    }
}

fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
    find_covering_element(syntax, range).ancestors().find_map(N::cast)
}

fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
    match node.ancestors().find_map(N::cast) {
        None => false,
        Some(n) => n.syntax().text_range() == node.text_range(),
    }
}
<|endoftext|>