mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
189 lines
5.1 KiB
V
189 lines
5.1 KiB
V
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
module util
|
|
|
|
import os
|
|
import term
|
|
import v.token
|
|
import time
|
|
|
|
// The filepath:line:col: format is the default C compiler error output format.
|
|
// It allows editors and IDE's like emacs to quickly find the errors in the
|
|
// output and jump to their source with a keyboard shortcut.
|
|
// NB: using only the filename may lead to inability of IDE/editors
|
|
// to find the source file, when the IDE has a different working folder than
|
|
// v itself.
|
|
// error_context_before - how many lines of source context to print before the pointer line
|
|
// error_context_after - ^^^ same, but after
|
|
const (
|
|
error_context_before = 2
|
|
error_context_after = 2
|
|
)
|
|
|
|
// emanager.support_color - should the error and other messages
|
|
// have ANSI terminal escape color codes in them.
|
|
// By default, v tries to autodetect, if the terminal supports colors.
|
|
// Use -color and -nocolor options to override the detection decision.
|
|
pub const (
|
|
emanager = new_error_manager()
|
|
)
|
|
|
|
pub struct EManager {
|
|
mut:
|
|
support_color bool
|
|
}
|
|
|
|
pub fn new_error_manager() &EManager {
|
|
return &EManager{
|
|
support_color: term.can_show_color_on_stderr()
|
|
}
|
|
}
|
|
|
|
pub fn (e &EManager) set_support_color(b bool) {
|
|
e.support_color = b
|
|
}
|
|
|
|
fn bold(msg string) string {
|
|
if !emanager.support_color {
|
|
return msg
|
|
}
|
|
return term.bold(msg)
|
|
}
|
|
|
|
fn color(kind, msg string) string {
|
|
if !emanager.support_color {
|
|
return msg
|
|
}
|
|
if kind.contains('error') {
|
|
return term.red(msg)
|
|
} else {
|
|
return term.magenta(msg)
|
|
}
|
|
}
|
|
|
|
// formatted_error - `kind` may be 'error' or 'warn'
|
|
pub fn formatted_error(kind, emsg, filepath string, pos token.Position) string {
|
|
mut path := filepath
|
|
verror_paths_override := os.getenv('VERROR_PATHS')
|
|
if verror_paths_override == 'absolute' {
|
|
path = os.real_path(path)
|
|
} else {
|
|
// Get relative path
|
|
workdir := os.getwd() + os.path_separator
|
|
if path.starts_with(workdir) {
|
|
path = path.replace(workdir, '')
|
|
}
|
|
}
|
|
//
|
|
source := read_file(filepath) or {
|
|
''
|
|
}
|
|
mut p := imax(0, imin(source.len - 1, pos.pos))
|
|
if source.len > 0 {
|
|
for ; p >= 0; p-- {
|
|
if source[p] == `\r` || source[p] == `\n` {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
column := imax(0, pos.pos - p - 1)
|
|
position := '${path}:${pos.line_nr+1}:${util.imax(1,column+1)}:'
|
|
scontext := source_context(kind, source, column, pos).join('\n')
|
|
final_position := bold(position)
|
|
final_kind := bold(color(kind, kind))
|
|
final_msg := emsg
|
|
final_context := if scontext.len > 0 { '\n$scontext' } else { '' }
|
|
//
|
|
return '$final_position $final_kind $final_msg $final_context'.trim_space()
|
|
}
|
|
|
|
pub fn source_context(kind, source string, column int, pos token.Position) []string {
|
|
mut clines := []string{}
|
|
if source.len == 0 {
|
|
return clines
|
|
}
|
|
source_lines := source.split_into_lines()
|
|
bline := imax(0, pos.line_nr - error_context_before)
|
|
aline := imax(0, imin(source_lines.len - 1, pos.line_nr + error_context_after))
|
|
tab_spaces := ' '
|
|
for iline := bline; iline <= aline; iline++ {
|
|
sline := source_lines[iline]
|
|
start_column := imin(column, sline.len)
|
|
end_column := imin(column + pos.len, sline.len)
|
|
cline := if iline == pos.line_nr {
|
|
sline[..start_column] + color(kind, sline[start_column..end_column]) + sline[end_column..]
|
|
} else {
|
|
sline
|
|
}
|
|
clines << '${iline+1:5d} | ' + cline.replace('\t', tab_spaces)
|
|
//
|
|
if iline == pos.line_nr {
|
|
// The pointerline should have the same spaces/tabs as the offending
|
|
// line, so that it prints the ^ character exactly on the *same spot*
|
|
// where it is needed. That is the reason we can not just
|
|
// use strings.repeat(` `, col) to form it.
|
|
mut pointerline := ''
|
|
for bchar in sline[..start_column] {
|
|
x := if bchar.is_space() {
|
|
bchar
|
|
} else {
|
|
` `
|
|
}
|
|
pointerline += x.str()
|
|
}
|
|
underline := if pos.len > 1 {
|
|
'~'.repeat(end_column - start_column)
|
|
} else {
|
|
'^'
|
|
}
|
|
pointerline += bold(color(kind, underline))
|
|
clines << ' | ' + pointerline.replace('\t', tab_spaces)
|
|
}
|
|
}
|
|
return clines
|
|
}
|
|
|
|
pub fn verror(kind, s string) {
|
|
final_kind := bold(color(kind, kind))
|
|
eprintln('${final_kind}: $s')
|
|
exit(1)
|
|
}
|
|
|
|
pub fn find_working_diff_command() ?string {
|
|
for diffcmd in ['colordiff', 'diff', 'colordiff.exe', 'diff.exe'] {
|
|
p := os.exec('$diffcmd --version') or {
|
|
continue
|
|
}
|
|
if p.exit_code == 0 {
|
|
return diffcmd
|
|
}
|
|
}
|
|
return error('no working diff command found')
|
|
}
|
|
|
|
pub fn color_compare_files(diff_cmd, file1, file2 string) string {
|
|
if diff_cmd != '' {
|
|
full_cmd := '$diff_cmd --minimal --text --unified=2 ' +
|
|
' --show-function-line="fn " "$file1" "$file2" '
|
|
x := os.exec(full_cmd) or {
|
|
return 'comparison command: `${full_cmd}` failed'
|
|
}
|
|
return x.output
|
|
}
|
|
return ''
|
|
}
|
|
|
|
pub fn color_compare_strings(diff_cmd string, expected string, found string) string {
|
|
cdir := os.cache_dir()
|
|
ctime := time.sys_mono_now()
|
|
e_file := os.join_path(cdir, '${ctime}.expected.txt')
|
|
f_file := os.join_path(cdir, '${ctime}.found.txt')
|
|
os.write_file( e_file, expected)
|
|
os.write_file( f_file, found)
|
|
res := util.color_compare_files(diff_cmd, e_file, f_file)
|
|
os.rm( e_file )
|
|
os.rm( f_file )
|
|
return res
|
|
}
|