def do_get_col_num_rfc_lines(view, cur_line, cnum, start_line, end_line, delim, expected_num_fields): cursor_line_offset = cur_line - start_line lines = [] for l in range(start_line, end_line + 1): lines.append(get_line_text(view, l)) record_str = '\n'.join(lines) fields, has_warning = csv_utils.smart_split( record_str, delim, 'quoted_rfc', preserve_quotes_and_whitespaces=True) if has_warning or len(fields) != expected_num_fields: return None current_line_offset = 0 col_num = 0 while col_num < len(fields): current_line_offset += fields[col_num].count('\n') if current_line_offset >= cursor_line_offset: break col_num += 1 if current_line_offset > cursor_line_offset: # The cursor line is inside the multiline col_num field return col_num if current_line_offset < cursor_line_offset: # Should never happend return None length_of_previous_field_segment_on_cursor_line = 0 if current_line_offset > 0: length_of_previous_field_segment_on_cursor_line = len( fields[col_num].split('\n')[-1]) + len(delim) if cnum <= length_of_previous_field_segment_on_cursor_line: return col_num col_num += 1 col_num = col_num + get_col_num_single_line( fields[col_num:], len(delim), cnum, length_of_previous_field_segment_on_cursor_line) return col_num
def csv_lint(view, delim, policy): if policy == 'quoted_rfc': # FIXME support csv_lint for rfc policy sublime.error_message( 'CSVLint is currently not supported for RFC4180-compatible dialects' ) return False num_fields = None line_regions = view.lines(sublime.Region(0, view.size())) for ln, lr in enumerate(line_regions): line = view.substr(lr) fields, warning = csv_utils.smart_split(line, delim, policy, True) if warning: sublime.error_message( 'CSVLint: line {} has formatting error: double quote chars are not consistent' .format(ln + 1)) return False if num_fields is None: num_fields = len(fields) if num_fields != len(fields): sublime.error_message( 'Number of fields is not consistent: e.g. line 1 has {} fields, and line {} has {} fields' .format(num_fields, ln + 1, len(fields))) return False return True
def run(self, edit): dialect = get_dialect(self.view.settings()) if dialect[1] == 'monocolumn': sublime.error_message( 'Error. You need to select a separator first') return delim, policy = dialect adjusted_lines = [] has_edit = False line_regions = self.view.lines(sublime.Region(0, self.view.size())) for ln, lr in enumerate(line_regions): line = self.view.substr(lr) fields, warning = csv_utils.smart_split(line, delim, policy, True) if warning: sublime.error_message( 'Unable to Shrink: line {} has formatting error: double quote chars are not consistent' .format(ln + 1)) return for i in range(len(fields)): adjusted = fields[i].strip() if len(adjusted) != len(fields[i]): fields[i] = adjusted has_edit = True adjusted_lines.append(delim.join(fields)) if not has_edit: sublime.message_dialog('Table is already shrinked, skipping') return adjusted_content = '\n'.join(adjusted_lines) self.view.replace(edit, sublime.Region(0, self.view.size()), adjusted_content)
def show_names_for_line(view, delim, policy, line_region): point = line_region.a line_text = view.substr(line_region) fields = csv_utils.smart_split(line_text, delim, policy, True)[0] tab_stop = view.settings().get('tab_size', 4) if delim == '\t' else 1 layout_width_dip = view.layout_extent()[0] font_char_width_dip = view.em_width() dip_reserve = 10 char_reserve = 2 max_status_width = layout_width_dip - dip_reserve max_available_chars = max_status_width // font_char_width_dip - char_reserve status_labels = generate_tab_statusline(tab_stop, len(delim), fields, max_available_chars) if not len(status_labels): return num_fields = len(status_labels) // 2 html_text = '' for i in range(num_fields): hex_color = get_column_color(view, i) column_name = status_labels[i * 2] space_filling = status_labels[i * 2 + 1].replace(' ', ' ') html_text += '<span style="color:{}">{}{}</span>'.format( hex_color, html_escape(column_name), space_filling) view.show_popup(html_text, location=point, max_width=max_status_width, max_height=100)
def on_hover(self, point, hover_zone): if hover_zone == sublime.HOVER_TEXT: dialect = get_dialect(self.view.settings()) if not dialect: return delim, policy = dialect # lnum and cnum are 0-based cnum = self.view.rowcol(point)[1] line_text = self.view.substr(self.view.line(point)) hover_record, warning = csv_utils.smart_split( line_text, delim, policy, True) field_num = get_field_by_line_position(hover_record, cnum) header = get_document_header(self.view, delim, policy) ui_text = 'Col #{}'.format(field_num + 1) if field_num < len(header): column_name = header[field_num] max_header_len = 30 if len(column_name) > max_header_len: column_name = column_name[:max_header_len] + '...' ui_text += ', Header: "{}"'.format(column_name) if len(header) != len(hover_record): ui_text += '; WARN: num of fields in Header and this line differs' if warning: ui_text += '; This line has quoting error' ui_hex_color = get_column_color(self.view, field_num) self.view.show_popup('<span style="color:{}">{}</span>'.format( ui_hex_color, ui_text), sublime.HIDE_ON_MOUSE_MOVE_AWAY, point, on_hide=hover_hide_cb, max_width=1000)
def on_hover(self, point, hover_zone): if hover_zone == sublime.HOVER_TEXT: dialect = get_dialect(self.view.settings()) if dialect[1] == 'monocolumn': return delim, policy = dialect header = get_document_header(self.view, delim, policy) # lnum and cnum are 0-based cnum = self.view.rowcol(point)[1] quoting_warning = False inconsistent_num_fields_warning = False if policy == 'quoted_rfc': try: field_num = get_col_num_rfc_lines(self.view, delim, point, len(header)) if field_num is None: self.view.show_popup( '<span>Unable to infer column id</span>', sublime.HIDE_ON_MOUSE_MOVE_AWAY, point, on_hide=hover_hide_cb, max_width=1000) return except Exception as e: print( 'Rainbow CSV: Unexpected Exception while showing column info: {}' .format(e)) return else: line_text = self.view.substr(self.view.line(point)) hover_record, quoting_warning = csv_utils.smart_split( line_text, delim, policy, True) field_num = get_col_num_single_line(hover_record, len(delim), cnum) if len(header) != len(hover_record): inconsistent_num_fields_warning = True use_zero_based_column_indices = get_setting( self.view, 'use_zero_based_column_indices', False) ui_text = 'Col {}'.format( field_num if use_zero_based_column_indices else field_num + 1) if field_num < len(header): column_name = header[field_num] max_header_len = 30 if len(column_name) > max_header_len: column_name = column_name[:max_header_len] + '...' ui_text += ', {}'.format(column_name) if inconsistent_num_fields_warning: ui_text += '; WARN: num of fields in Header and this line differs' if quoting_warning: ui_text += '; This line has quoting error' ui_hex_color = get_column_color(self.view, field_num) self.view.show_popup('<span style="color:{}">{}</span>'.format( ui_hex_color, html_escape(ui_text)), sublime.HIDE_ON_MOUSE_MOVE_AWAY, point, on_hide=hover_hide_cb, max_width=1000)
def is_delimited_table(sampled_lines, delim, policy, min_num_lines): if len(sampled_lines) < min_num_lines: return False num_fields = None for sl in sampled_lines: fields, warning = csv_utils.smart_split(sl, delim, policy, True) if warning or len(fields) < 2: return False if num_fields is None: num_fields = len(fields) if num_fields != len(fields): return False return True
def calc_column_sizes(view, delim, policy): result = [] line_regions = view.lines(sublime.Region(0, view.size())) for ln, lr in enumerate(line_regions): line = view.substr(lr) fields, warning = csv_utils.smart_split(line, delim, policy, True) if warning: return (None, ln) for i in range(len(fields)): if len(result) <= i: result.append(0) result[i] = max(result[i], len(fields[i].strip())) return (result, None)
def csv_lint(view, delim, policy): num_fields = None line_regions = view.lines(sublime.Region(0, view.size())) for ln, lr in enumerate(line_regions): line = view.substr(lr) fields, warning = csv_utils.smart_split(line, delim, policy, True) if warning: sublime.error_message( 'CSVLint: line {} has formatting error: double quote chars are not consistent' .format(ln + 1)) return False if num_fields is None: num_fields = len(fields) if num_fields != len(fields): sublime.error_message( 'Number of fields is not consistent: e.g. line 1 has {} fields, and line {} has {} fields' .format(num_fields, ln + 1, len(fields))) return False return True
def run(self, edit): dialect = get_dialect(self.view.settings()) if dialect[1] == 'monocolumn': sublime.error_message( 'Error. You need to select a separator first') return delim, policy = dialect column_sizes, failed_line_num = calc_column_sizes( self.view, delim, policy) if failed_line_num is not None: sublime.error_message( 'Unable to Align: line {} has formatting error: double quote chars are not consistent' .format(failed_line_num + 1)) return adjusted_lines = [] has_edit = False line_regions = self.view.lines(sublime.Region(0, self.view.size())) for lr in line_regions: line = self.view.substr(lr) fields = csv_utils.smart_split(line, delim, policy, True)[0] for i in range(0, len(fields) - 1): if i >= len(column_sizes): break adjusted = fields[i].strip() delta_len = column_sizes[i] - len(adjusted) if delta_len >= 0: # Safeguard against async doc edit adjusted += ' ' * (delta_len + 1) if fields[i] != adjusted: fields[i] = adjusted has_edit = True adjusted_lines.append(delim.join(fields)) if not has_edit: sublime.message_dialog('Table is already aligned, skipping') return adjusted_content = '\n'.join(adjusted_lines) self.view.replace(edit, sublime.Region(0, self.view.size()), adjusted_content)
def get_document_header(view, delim, policy): header_line = get_line_text(view, 0) return csv_utils.smart_split(header_line, delim, policy, False)[0]
def get_col_num_rfc_basic_even_case(line, cnum, delim, expected_num_fields): fields, warning = csv_utils.smart_split( line, delim, 'quoted_rfc', preserve_quotes_and_whitespaces=True) if warning or len(fields) != expected_num_fields: return None return get_col_num_single_line(fields, len(delim), cnum)