Repository: typst/typst
File: crates/typst-cli/src/compile.rs (Rust)

Code before:
        .map_err(|err| {
            eco_format!("failed to create make dependencies file due to IO error ({err})")
        })
}

/// Opens the given file using:
/// - The default file viewer if `open` is `None`.
/// - The given viewer provided by `open` if it is `Some`.
fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> {
    if let Some(app) = open {
        open::with_in_background(path, app);
    } else {
        open::that_in_background(path);
    }

    Ok(())
}

/// Checks if the main file can be read and opened.
/// Returns a boolean indicating whether that is the case.
/// If an unexpected error occurred, returns it.
fn check_main_file_is_valid(
    world: &SystemWorld,
    command: &CompileCommand,
    watching: bool,
) -> StrResult<bool> {
    let main_result = world.source(world.main());
    let Err(file_error) = &main_result else {
        return Ok(true);
    };

    set_failed();
    if watching {
        Status::Error.print(command).unwrap();
    }

    let is_utf8_error = matches!(file_error, FileError::InvalidUtf8);
    let mut main_result = main_result.at(Span::detached());

    // Attempt to provide helpful hints for UTF-8 errors.
    // Perhaps the user mistyped the filename.
    // For example, they could have written "file.pdf" instead of
    // "file.typ".
    if is_utf8_error {
        if let Input::Path(path) = &command.common.input {
            let extension = path.extension();
            let exists_typ_file = path.with_extension("typ").exists();

            let hint_given = match extension {
                Some(extension) if extension != "typ" => {
                    main_result = main_result.hint(eco_format!(

Review comment (suggestion):
With a signature of `(file_error: FileError, input: &Input) -> EcoVec<SourceDiagnostic>`, we could then construct a single `SourceDiagnostic::error` here, and use its `.hint` method to add hints.

Code after:
        }
        file.write_all(b"\n")?;

        Ok(())
    }

    write(make_deps_path, output_path, world.root().to_owned(), world.dependencies())
        .map_err(|err| {
            eco_format!("failed to create make dependencies file due to IO error ({err})")
        })
}

/// Opens the given file using:
/// - The default file viewer if `open` is `None`.
/// - The given viewer provided by `open` if it is `Some`.
fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> {
    if let Some(app) = open {
        open::with_in_background(path, app);
    } else {
        open::that_in_background(path);
    }

    Ok(())
}

/// Adds useful hints when the main source file couldn't be read
/// and returns the final diagnostic.
fn hint_invalid_main_file(
    file_error: FileError,
    input: &Input,
) -> EcoVec<SourceDiagnostic> {
    let is_utf8_error = matches!(file_error, FileError::InvalidUtf8);
    let mut diagnostic =
        SourceDiagnostic::error(Span::detached(), EcoString::from(file_error));

    // Attempt to provide helpful hints for UTF-8 errors.
    // Perhaps the user mistyped the filename.
    // For example, they could have written "file.pdf" instead of
    // "file.typ".
    if is_utf8_error {
        if let Input::Path(path) = input {
            let extension = path.extension();
            if extension.is_some_and(|extension| extension == "typ") {
                // No hints if the file is already a .typ file.
                // The file is indeed just invalid.
                return eco_vec![diagnostic];
            }

            match extension {
                Some(extension) => {
                    diagnostic.hint(eco_format!(