///|
/// 在文本中用 `**term**` 标记查询命中的词,返回高亮后的文本。
///
/// 长词优先、区间不重叠,避免中文单字与 bigram 同时命中时重复标记。
pub fn highlight(
  text : String,
  query : String,
  tokenizer? : Tokenizer = Tokenizer::new(),
) -> String {
  let tokens = tokenizer.tokenize(query)
  let terms : Array[String] = []
  for t in tokens {
    if !terms.contains(t) {
      terms.push(t)
    }
  }
  terms.sort_by(fn(a, b) { b.length().compare(a.length()) })
  let ranges : Array[(Int, Int)] = []
  let mut pos = 0
  while pos < text.length() {
    let mut best : (Int, Int)? = None
    for term in terms {
      match text.exact_view(start=pos, end=text.length()).find(term) {
        None => continue
        Some(off) => {
          let s = pos + off
          let e = s + term.length()
          match best {
            None => best = Some((s, e))
            Some((bs, _)) => if s < bs { best = Some((s, e)) }
          }
        }
      }
    }
    match best {
      None => break
      Some((s, e)) => {
        ranges.push((s, e))
        pos = e
      }
    }
  }
  let merged : Array[(Int, Int)] = []
  for r in ranges {
    let (s, e) = r
    if merged.is_empty() {
      merged.push((s, e))
    } else {
      let (ps, pe) = merged[merged.length() - 1]
      if s == pe {
        merged[merged.length() - 1] = (ps, e)
      } else {
        merged.push((s, e))
      }
    }
  }
  let sb = StringBuilder()
  let mut last = 0
  for r in merged {
    let (s, e) = r
    sb.write_string(text.exact_view(start=last, end=s).to_owned())
    sb.write_string("**")
    sb.write_string(text.exact_view(start=s, end=e).to_owned())
    sb.write_string("**")
    last = e
  }
  sb.write_string(text.exact_view(start=last, end=text.length()).to_owned())
  sb.to_string()
}