Repository: helix-editor/helix
File: helix-core/src/comment.rs (Rust)

Code before:

use crate::{
    syntax::BlockCommentToken, Change, Range, Rope, RopeSlice, Selection, Tendril, Transaction,
};
use helix_stdx::rope::RopeSliceExt;
use std::borrow::Cow;

pub const DEFAULT_COMMENT_TOKEN: &str = "//";

/// Returns the longest matching comment token of the given line (if it exists).
pub fn get_comment_token<'a, S: AsRef<str>>(
    text: RopeSlice,
    tokens: &'a [S],
    line_num: usize,
) -> Option<&'a str> {
    let mut used_token: Option<&'a str> = None;

    let line = text.line(line_num);
    let start = line.first_non_whitespace_char()?;

    let mut max_token_len = 0;
    for token in tokens {
        let token_str = token.as_ref();
        let end = std::cmp::min(start + token_str.len(), line.len_chars());

        let matches_token = line.slice(start..end).starts_with(token_str);
        if matches_token && token_str.len() >= max_token_len {
            used_token = Some(token_str);
            max_token_len = token_str.len();
        }
    }

    used_token
}

/// Given text, a comment token, and a set of line indices, returns the following:
/// - Whether the given lines should be considered commented
///     - If any of the lines are uncommented, all lines are considered as such.
/// - The lines to change for toggling comments
///     - This is all provided lines excluding blanks lines.
/// - The column of the comment tokens
///     - Column of existing tokens, if the lines are commented; column to place tokens at otherwise.
/// - The margin to the right of the comment tokens
///     - Defaults to `1`. If any existing comment token is not followed by a space, changes to `0`.
fn find_line_comment(
    token: &str,
    text: RopeSlice,
    lines: impl IntoIterator<Item = usize>,
) -> (bool, Vec<usize>, usize, usize) {
    let mut commented = true;
    let mut text_is_empty = true;

Review comment (suggestion):
This is mixing up the bytes/chars indices since `token_str.len()` is the number of bytes but we're treating it as the number of chars. It won't panic since we're capping it to the length in chars but if there were non-ASCII comment tokens there would be a correctness issue I believe.

We can use `slice(start..)` here anyways though and avoid computing `end` at all since we're using `starts_with`

Code after:

use crate::{
    syntax::BlockCommentToken, Change, Range, Rope, RopeSlice, Selection, Tendril, Transaction,
};
use helix_stdx::rope::RopeSliceExt;
use std::borrow::Cow;

pub const DEFAULT_COMMENT_TOKEN: &str = "//";

/// Returns the longest matching comment token of the given line (if it exists).
pub fn get_comment_token<'a, S: AsRef<str>>(
    text: RopeSlice,
    tokens: &'a [S],
    line_num: usize,
) -> Option<&'a str> {
    let line = text.line(line_num);
    let start = line.first_non_whitespace_char()?;

    tokens
        .iter()
        .map(AsRef::as_ref)
        .filter(|token| line.slice(start..).starts_with(token))
        .max_by_key(|token| token.len())
}

/// Given text, a comment token, and a set of line indices, returns the following:
/// - Whether the given lines should be considered commented
///     - If any of the lines are uncommented, all lines are considered as such.
/// - The lines to change for toggling comments
///     - This is all provided lines excluding blanks lines.
/// - The column of the comment tokens
///     - Column of existing tokens, if the lines are commented; column to place tokens at otherwise.
/// - The margin to the right of the comment tokens
///     - Defaults to `1`. If any existing comment token is not followed by a space, changes to `0`.
fn find_line_comment(
    token: &str,
    text: RopeSlice,
    lines: impl IntoIterator<Item = usize>,
) -> (bool, Vec<usize>, usize, usize) {
    let mut commented = true;
    let mut to_change = Vec::new();
    let mut min = usize::MAX; // minimum col for first_non_whitespace_char
    let mut margin = 1;
    let token_len = token.chars().count();

    for line in lines {
        let line_slice = text.line(line);
        if let Some(pos) = line_slice.first_non_whitespace_char() {
            let len = line_slice.len_chars();

            min = std::cmp::min(min, pos);