|
1 |
| -use crate::config::CONFIG; |
| 1 | +use annotate_snippets::{ |
| 2 | + display_list::{DisplayList, FormatOptions}, |
| 3 | + snippet::{AnnotationType, Slice, Snippet, SourceAnnotation}, |
| 4 | +}; |
| 5 | +use itertools::Itertools; |
| 6 | + |
| 7 | +use crate::{config::CONFIG, cst::Location, error::UroboroSQLFmtError}; |
2 | 8 |
|
3 | 9 | /// 設定ファイルに合わせて予約後の大文字・小文字を変換する
|
4 | 10 | pub(crate) fn convert_keyword_case(keyword: &str) -> String {
|
@@ -72,3 +78,91 @@ pub(crate) fn is_line_overflow(char_len: usize) -> bool {
|
72 | 78 | char_len >= max_char_per_line as usize
|
73 | 79 | }
|
74 | 80 | }
|
| 81 | + |
| 82 | +/// xバイト目が何文字目かを返す |
| 83 | +fn byte_to_char_index(input: &str, target_byte_index: usize) -> Result<usize, UroboroSQLFmtError> { |
| 84 | + let mut char_index = 0; |
| 85 | + let mut byte_index = 0; |
| 86 | + |
| 87 | + for c in input.chars() { |
| 88 | + if byte_index == target_byte_index { |
| 89 | + return Ok(char_index); |
| 90 | + } |
| 91 | + char_index += 1; |
| 92 | + byte_index += c.len_utf8(); |
| 93 | + } |
| 94 | + |
| 95 | + if byte_index == target_byte_index { |
| 96 | + Ok(char_index) |
| 97 | + } else { |
| 98 | + Err(UroboroSQLFmtError::Runtime(format!( |
| 99 | + "byte_to_char_index: byte_index({}) is out of range", |
| 100 | + target_byte_index |
| 101 | + ))) |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +/// エラー注釈を作成する関数 |
| 106 | +/// 以下の形のエラー注釈を生成 |
| 107 | +/// |
| 108 | +/// ```sh |
| 109 | +/// | |
| 110 | +/// 2 | using tbl_b b |
| 111 | +/// | ^^^^^^^^^^^^^ {label} |
| 112 | +/// | |
| 113 | +/// ``` |
| 114 | +pub(crate) fn create_error_annotation( |
| 115 | + location: &Location, |
| 116 | + label: &str, |
| 117 | + src: &str, |
| 118 | +) -> Result<String, UroboroSQLFmtError> { |
| 119 | + // 元のSQLのエラーが発生した行を取得 |
| 120 | + let source = src |
| 121 | + .lines() |
| 122 | + .enumerate() |
| 123 | + .filter(|(i, _)| (location.start_position.row..=location.end_position.row).contains(i)) |
| 124 | + .map(|(_, x)| x) |
| 125 | + .join("\n"); |
| 126 | + |
| 127 | + // エラー発生箇所の開始位置 |
| 128 | + // locaton.start_position.colはバイト数を指しているので文字数に変換する |
| 129 | + let start_point = byte_to_char_index( |
| 130 | + src.lines().collect_vec()[location.start_position.row], |
| 131 | + location.start_position.col, |
| 132 | + )?; |
| 133 | + |
| 134 | + // エラー発生箇所の終了位置 |
| 135 | + // = (終了位置までの行の文字数合計) + (終了位置の行の終了位置までの文字数) |
| 136 | + let end_point = src |
| 137 | + .lines() |
| 138 | + .enumerate() |
| 139 | + .filter(|(i, _)| (location.start_position.row..location.end_position.row).contains(i)) |
| 140 | + .map(|(_, x)| x.chars().count() + 1) // 改行コードの分1プラスする |
| 141 | + .sum::<usize>() |
| 142 | + + byte_to_char_index( |
| 143 | + src.lines().collect_vec()[location.end_position.row], |
| 144 | + location.end_position.col, |
| 145 | + )?; // エラー発生行の終了位置までの文字数 (locaton.end_position.colはバイト数を指しているので文字数に変換する) |
| 146 | + |
| 147 | + let snippet = Snippet { |
| 148 | + title: None, |
| 149 | + footer: vec![], |
| 150 | + slices: vec![Slice { |
| 151 | + source: &source, |
| 152 | + line_start: location.start_position.row + 1, |
| 153 | + origin: None, |
| 154 | + fold: true, |
| 155 | + annotations: vec![SourceAnnotation { |
| 156 | + label, |
| 157 | + annotation_type: AnnotationType::Error, |
| 158 | + range: (start_point, end_point), |
| 159 | + }], |
| 160 | + }], |
| 161 | + opt: FormatOptions { |
| 162 | + color: true, |
| 163 | + ..Default::default() |
| 164 | + }, |
| 165 | + }; |
| 166 | + |
| 167 | + Ok(DisplayList::from(snippet).to_string()) |
| 168 | +} |
0 commit comments