diff --git a/src/display_buffer.cc b/src/display_buffer.cc index 6aaa0843..4b4847cb 100644 --- a/src/display_buffer.cc +++ b/src/display_buffer.cc @@ -1,188 +1,20 @@ #include "display_buffer.hh" #include "assert.hh" -#include namespace Kakoune { -String DisplayAtom::content() const +DisplayLine::iterator DisplayLine::split(iterator it, BufferIterator pos) { - switch (m_content_mode) - { - case BufferText: - return m_begin.buffer().string(m_begin, m_end); - case ReplacementText: - return m_replacement_text; - } - assert(false); - return ""; -} + assert(it->content.type() == AtomContent::BufferRange); + assert(it->content.begin() < pos); + assert(it->content.end() > pos); -static DisplayCoord advance_coord(const DisplayCoord& pos, - const BufferIterator& begin, - const BufferIterator& end) -{ - if (begin.line() == end.line()) - return DisplayCoord(pos.line, pos.column + end.column() - begin.column()); - else - return DisplayCoord(pos.line + end.line() - begin.line(), end.column()); -} - -static DisplayCoord advance_coord(const DisplayCoord& pos, - const String& str) -{ - DisplayCoord res = pos; - for (auto c : str) - { - if (c == '\n') - { - ++res.line; - res.column = 0; - } - else - ++res.column; - } - return res; -} - -DisplayCoord DisplayAtom::end_coord() const -{ - switch (m_content_mode) - { - case BufferText: - return advance_coord(m_coord, m_begin, m_end); - case ReplacementText: - return advance_coord(m_coord, m_replacement_text); - } - assert(false); - return { 0, 0 }; -} - -BufferIterator DisplayAtom::iterator_at(const DisplayCoord& coord) const -{ - if (m_content_mode != BufferText or coord <= m_coord) - return m_begin; - - DisplayCoord pos = m_coord; - for (BufferIterator it = m_begin; it != m_end; ++it) - { - if (*it == '\n') - { - ++pos.line; - pos.column = 0; - } - else - ++pos.column; - - if (coord <= pos) - return it+1; - } - return m_end; -} - -DisplayCoord DisplayAtom::line_and_column_at(const BufferIterator& iterator) const -{ - assert(iterator >= m_begin and iterator < m_end); - - if (m_content_mode != BufferText) - return m_coord; - - return advance_coord(m_coord, m_begin, iterator); -} - -DisplayBuffer::DisplayBuffer() -{ -} - -DisplayBuffer::iterator DisplayBuffer::append(BufferIterator begin, BufferIterator end) -{ - DisplayCoord coord; - if (not m_atoms.empty()) - coord = m_atoms.back().end_coord(); - m_atoms.push_back(DisplayAtom(std::move(coord), std::move(begin), std::move(end))); - return --(this->end()); -} - -DisplayBuffer::iterator DisplayBuffer::insert_empty_atom_before(iterator where) -{ - assert(where != end()); - iterator res = m_atoms.insert( - where, DisplayAtom(where->coord(), where->begin(), where->begin())); - return res; -} - -DisplayBuffer::iterator DisplayBuffer::atom_containing(const BufferIterator& where) -{ - return atom_containing(where, m_atoms.begin()); -} - -DisplayBuffer::iterator DisplayBuffer::atom_containing(const BufferIterator& where, - iterator start) -{ - if (start == end() or where < start->begin()) - return end(); - - while (start != end()) - { - if (start->end() > where) - break; - ++start; - } - return start; -} - -DisplayBuffer::iterator DisplayBuffer::split(iterator atom, const BufferIterator& pos) -{ - assert(atom->splitable()); - assert(pos > atom->begin()); - assert(pos < atom->end()); - - BufferIterator end = atom->m_end; - atom->m_end = pos; - - DisplayAtom new_atom(atom->end_coord(), pos, end, - atom->fg_color(), atom->bg_color(), atom->attribute()); - - iterator insert_pos = atom; - ++insert_pos; - m_atoms.insert(insert_pos, std::move(new_atom)); - check_invariant(); - return atom; -} - -void DisplayBuffer::check_invariant() const -{ - const_iterator prev_it; - for (const_iterator it = begin(); it != end(); ++it) - { - assert(it->end() >= it->begin()); - if (it != begin()) - { - assert(prev_it->end() == it->begin()); - assert(prev_it->end_coord() == it->coord()); - } - prev_it = it; - } -} - -void DisplayBuffer::replace_atom_content(iterator atom, - const String& replacement) -{ - atom->m_content_mode = DisplayAtom::ReplacementText; - atom->m_replacement_text = replacement; - - // update coordinates of subsequents atoms - DisplayCoord new_coord = atom->end_coord(); - while (true) - { - new_coord = atom->end_coord(); - ++atom; - - if (atom == end() or new_coord == atom->m_coord) - break; - atom->m_coord = new_coord; - } + DisplayAtom atom = *it; + atom.content.end() = pos; + it->content.begin() = pos; + return m_atoms.insert(it, std::move(atom)); } } diff --git a/src/display_buffer.hh b/src/display_buffer.hh index deb31f04..685532e9 100644 --- a/src/display_buffer.hh +++ b/src/display_buffer.hh @@ -28,8 +28,7 @@ enum Attributes Underline = 1, Reverse = 2, Blink = 4, - Bold = 8, - Final = 16 + Bold = 8 }; enum class Color @@ -45,99 +44,116 @@ enum class Color White }; -// A DisplayAtom is a string of text with it's display style. -// -// The DisplayAtom class references the buffer string it represents -// with it's begin/end iterators and may replace it with another -// text stored in the replacement_string field. -struct DisplayAtom +struct AtomContent { - const DisplayCoord& coord() const { return m_coord; } - const BufferIterator& begin() const { return m_begin; } - const BufferIterator& end() const { return m_end; } - const Color& fg_color() const { return m_fg_color; } - const Color& bg_color() const { return m_bg_color; } - const Attribute& attribute() const { return m_attribute; } +public: + enum Type { BufferRange, ReplacedBufferRange, Text }; - enum ContentMode + AtomContent(BufferIterator begin, BufferIterator end) + : m_type(BufferRange), + m_begin(std::move(begin)), + m_end(std::move(end)) {} + + AtomContent(String str) + : m_type(Text), m_text(std::move(str)) {} + + String content() const { - BufferText, - ReplacementText - }; - ContentMode content_mode() const { return m_content_mode; } + switch (m_type) + { + case BufferRange: + return m_begin.buffer().string(m_begin, m_end); + case Text: + case ReplacedBufferRange: + return m_text; + } + } - Color& fg_color() { return m_fg_color; } - Color& bg_color() { return m_bg_color; } - Attribute& attribute() { return m_attribute; } + BufferIterator& begin() + { + assert(has_buffer_range()); + return m_begin; + } - String content() const; - DisplayCoord end_coord() const; - BufferIterator iterator_at(const DisplayCoord& coord) const; - DisplayCoord line_and_column_at(const BufferIterator& iterator) const; + BufferIterator& end() + { + assert(has_buffer_range()); + return m_end; + } - bool splitable() const { return m_content_mode != ReplacementText; } + void replace(String text) + { + assert(m_type == BufferRange); + m_type = ReplacedBufferRange; + m_text = std::move(text); + } + + bool has_buffer_range() const + { + return m_type == BufferRange or m_type == ReplacedBufferRange; + } + + Type type() const { return m_type; } private: - friend class DisplayBuffer; - DisplayAtom(DisplayCoord coord, - BufferIterator begin, BufferIterator end, - Color fg_color = Color::Default, - Color bg_color = Color::Default, - Attribute attribute = Attributes::Normal) - : m_content_mode(BufferText), - m_coord(std::move(coord)), - m_begin(std::move(begin)), m_end(std::move(end)), - m_fg_color(fg_color), - m_bg_color(bg_color), - m_attribute(attribute) - {} + Type m_type; - ContentMode m_content_mode; - - DisplayCoord m_coord; BufferIterator m_begin; BufferIterator m_end; - Color m_fg_color; - Color m_bg_color; - Attribute m_attribute; - String m_replacement_text; + String m_text; +}; + +struct DisplayAtom +{ + Color fg_color; + Color bg_color; + Attribute attribute; + + AtomContent content; + + DisplayAtom(AtomContent content) + : content(std::move(content)), attribute(Normal), + fg_color(Color::Default), bg_color(Color::Default) {} +}; + +class DisplayLine +{ +public: + using AtomList = std::vector; + using iterator = AtomList::iterator; + using const_iterator = AtomList::const_iterator; + + explicit DisplayLine(size_t buffer_line) : m_buffer_line(buffer_line) {} + + size_t buffer_line() const { return m_buffer_line; } + + iterator begin() { return m_atoms.begin(); } + iterator end() { return m_atoms.end(); } + + const_iterator begin() const { return m_atoms.begin(); } + const_iterator end() const { return m_atoms.end(); } + + // Split atom pointed by it at pos, returns an iterator to the first atom + iterator split(iterator it, BufferIterator pos); + + iterator insert(iterator it, DisplayAtom atom) { return m_atoms.insert(it, std::move(atom)); } + void push_back(DisplayAtom atom) { m_atoms.push_back(std::move(atom)); } + +private: + size_t m_buffer_line; + AtomList m_atoms; }; -// A DisplayBuffer is the visual content of a Window as a DisplayAtom list -// -// The DisplayBuffer class provides means to mutate and iterator on it's -// DisplayAtoms. class DisplayBuffer { public: - typedef std::list AtomList; - typedef AtomList::iterator iterator; - typedef AtomList::const_iterator const_iterator; + using LineList = std::list; + DisplayBuffer() {} - DisplayBuffer(); - - void clear() { m_atoms.clear(); } - iterator append(BufferIterator begin, BufferIterator end); - iterator insert_empty_atom_before(iterator where); - iterator split(iterator atom, const BufferIterator& pos); - - void replace_atom_content(iterator atom, const String& replacement); - - iterator begin() { return m_atoms.begin(); } - iterator end() { return m_atoms.end(); } - - const_iterator begin() const { return m_atoms.begin(); } - const_iterator end() const { return m_atoms.end(); } - - iterator atom_containing(const BufferIterator& where); - iterator atom_containing(const BufferIterator& where, iterator start); - - const DisplayAtom& front() const { return m_atoms.front(); } - const DisplayAtom& back() const { return m_atoms.back(); } - - void check_invariant() const; + LineList& lines() { return m_lines; } + const LineList& lines() const { return m_lines; } private: - AtomList m_atoms; + LineList m_lines; }; } diff --git a/src/highlighters.cc b/src/highlighters.cc index e59315ef..c34635f4 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -2,7 +2,6 @@ #include "assert.hh" #include "window.hh" -#include "display_buffer.hh" #include "highlighter_registry.hh" #include "highlighter_group.hh" #include "regex.hh" @@ -14,66 +13,57 @@ using namespace std::placeholders; typedef boost::regex_iterator RegexIterator; -void colorize_regex_range(DisplayBuffer& display_buffer, - const BufferIterator& range_begin, - const BufferIterator& range_end, - const Regex& ex, - Color fg_color, Color bg_color = Color::Default) +template +void highlight_range(DisplayBuffer& display_buffer, + BufferIterator begin, BufferIterator end, + bool skip_replaced, T func) { - assert(range_begin <= range_end); - - if (range_begin >= display_buffer.back().end() or - range_end <= display_buffer.front().begin()) - return; - - BufferIterator display_begin = std::max(range_begin, - display_buffer.front().begin()); - BufferIterator display_end = std::min(range_end, - display_buffer.back().end()); - - RegexIterator re_it(display_begin, display_end, ex, boost::match_nosubs); - RegexIterator re_end; - DisplayBuffer::iterator atom_it = display_buffer.begin(); - for (; re_it != re_end; ++re_it) + for (auto& line : display_buffer.lines()) { - BufferIterator begin = (*re_it)[0].first; - BufferIterator end = (*re_it)[0].second; - assert(begin != end); - - auto begin_atom_it = display_buffer.atom_containing(begin, atom_it); - assert(begin_atom_it != display_buffer.end()); - if (begin_atom_it->begin() != begin) + if (line.buffer_line() >= begin.line() and line.buffer_line() <= end.line()) { - if (begin_atom_it->splitable()) - begin_atom_it = ++display_buffer.split(begin_atom_it, begin); - else - ++begin_atom_it; + for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it) + { + bool is_replaced = atom_it->content.type() == AtomContent::ReplacedBufferRange; + + if (not atom_it->content.has_buffer_range() or + (skip_replaced and is_replaced)) + continue; + + if (end <= atom_it->content.begin() or begin >= atom_it->content.end()) + continue; + + if (not is_replaced and begin > atom_it->content.begin()) + atom_it = ++line.split(atom_it, begin); + + if (not is_replaced and end < atom_it->content.end()) + { + atom_it = line.split(atom_it, end); + func(*atom_it); + ++atom_it; + } + else + func(*atom_it); + } } - - auto end_atom_it = display_buffer.atom_containing(end, begin_atom_it); - if (end_atom_it != display_buffer.end() and - end_atom_it->begin() != end and end_atom_it->splitable()) - end_atom_it = ++display_buffer.split(end_atom_it, end); - - for (auto it = begin_atom_it; it != end_atom_it; ++it) - { - if (it->attribute() & Attributes::Final) - continue; - - it->fg_color() = fg_color; - it->bg_color() = bg_color; - } - - atom_it = end_atom_it; } } void colorize_regex(DisplayBuffer& display_buffer, + const Buffer& buffer, const Regex& ex, Color fg_color, Color bg_color = Color::Default) { - colorize_regex_range(display_buffer, display_buffer.front().begin(), - display_buffer.back().end(), ex, fg_color, bg_color); + RegexIterator re_it(buffer.begin(), buffer.end(), ex, boost::match_nosubs); + RegexIterator re_end; + for (; re_it != re_end; ++re_it) + { + highlight_range(display_buffer, (*re_it)[0].first, (*re_it)[0].second, true, + [&](DisplayAtom& atom) { + atom.fg_color = fg_color; + atom.bg_color = bg_color; + }); + } } Color parse_color(const String& color) @@ -103,55 +93,60 @@ HighlighterAndId colorize_regex_factory(Window& window, String id = "colre'" + params[0] + "'"; - return HighlighterAndId(id, std::bind(colorize_regex, _1, + return HighlighterAndId(id, std::bind(colorize_regex, + _1, std::ref(window.buffer()), ex, fg_color, bg_color)); } void expand_tabulations(Window& window, DisplayBuffer& display_buffer) { const int tabstop = window.option_manager()["tabstop"].as_int(); - for (auto atom_it = display_buffer.begin(); - atom_it != display_buffer.end(); ++atom_it) + for (auto& line : display_buffer.lines()) { - for (BufferIterator it = atom_it->begin(); it != atom_it->end(); ++it) + for (auto atom_it = line.begin(); atom_it != line.end(); ++atom_it) { - if (*it == '\t') + if (atom_it->content.type() != AtomContent::BufferRange) + continue; + + auto begin = atom_it->content.begin(); + auto end = atom_it->content.end(); + for (BufferIterator it = begin; it != end; ++it) { - if (it != atom_it->begin()) - atom_it = ++display_buffer.split(atom_it, it); - - if (it+1 != atom_it->end()) - atom_it = display_buffer.split(atom_it, it+1); - - BufferCoord pos = it.buffer().line_and_column_at(it); - - int column = 0; - for (auto line_it = it.buffer().iterator_at({pos.line, 0}); - line_it != it; ++line_it) + if (*it == '\t') { - assert(*line_it != '\n'); - if (*line_it == '\t') - column += tabstop - (column % tabstop); - else - ++column; - } + if (it != begin) + atom_it = ++line.split(atom_it, it); + if (it+1 != end) + atom_it = line.split(atom_it, it+1); - int count = tabstop - (column % tabstop); - String padding; - for (int i = 0; i < count; ++i) - padding += ' '; - display_buffer.replace_atom_content(atom_it, padding); + BufferCoord pos = it.buffer().line_and_column_at(it); + + int column = 0; + for (auto line_it = it.buffer().iterator_at({pos.line, 0}); + line_it != it; ++line_it) + { + assert(*line_it != '\n'); + if (*line_it == '\t') + column += tabstop - (column % tabstop); + else + ++column; + } + + int count = tabstop - (column % tabstop); + String padding; + for (int i = 0; i < count; ++i) + padding += ' '; + atom_it->content.replace(padding); + break; + } } } } } -void show_line_numbers(DisplayBuffer& display_buffer) +void show_line_numbers(Window& window, DisplayBuffer& display_buffer) { - const Buffer& buffer = display_buffer.front().begin().buffer(); - BufferCoord coord = buffer.line_and_column_at(display_buffer.begin()->begin()); - - int last_line = buffer.line_count(); + int last_line = window.buffer().line_count(); int digit_count = 0; for (int c = last_line; c > 0; c /= 10) ++digit_count; @@ -159,118 +154,30 @@ void show_line_numbers(DisplayBuffer& display_buffer) char format[] = "%?d "; format[1] = '0' + digit_count; - for (; coord.line <= last_line-1; ++coord.line) + for (auto& line : display_buffer.lines()) { - BufferIterator line_start = buffer.iterator_at(coord); - DisplayBuffer::iterator atom_it = display_buffer.atom_containing(line_start); - if (atom_it != display_buffer.end()) - { - if (atom_it->begin() != line_start) - { - if (not atom_it->splitable()) - continue; - - atom_it = ++display_buffer.split(atom_it, line_start); - } - atom_it = display_buffer.insert_empty_atom_before(atom_it); - atom_it->fg_color() = Color::Black; - atom_it->bg_color() = Color::White; - atom_it->attribute() = Attributes::Final; - - char buffer[10]; - snprintf(buffer, 10, format, coord.line + 1); - display_buffer.replace_atom_content(atom_it, buffer); - } + char buffer[10]; + snprintf(buffer, 10, format, line.buffer_line() + 1); + DisplayAtom atom = DisplayAtom(AtomContent(buffer)); + atom.fg_color = Color::Black; + atom.bg_color = Color::White; + line.insert(line.begin(), std::move(atom)); } } void highlight_selections(Window& window, DisplayBuffer& display_buffer) { - typedef std::pair BufferRange; - - std::vector selections; - for (auto& sel : window.selections()) - selections.push_back(BufferRange(sel.begin(), sel.end())); - - std::sort(selections.begin(), selections.end(), - [](const BufferRange& lhs, const BufferRange& rhs) - { return lhs.first < rhs.first; }); - - auto atom_it = display_buffer.begin(); - auto sel_it = selections.begin(); - - // underline each selections - while (atom_it != display_buffer.end() - and sel_it != selections.end()) - { - BufferRange& sel = *sel_it; - DisplayAtom& atom = *atom_it; - - if (atom.attribute() & Attributes::Final) - { - ++atom_it; - continue; - } - // [###------] - if (atom.begin() >= sel.first and atom.begin() < sel.second and - atom.end() > sel.second) - { - atom_it = display_buffer.split(atom_it, sel.second); - atom_it->attribute() |= Attributes::Underline; - ++atom_it; - ++sel_it; - } - // [---###---] - else if (atom.begin() < sel.first and atom.end() > sel.second) - { - atom_it = display_buffer.split(atom_it, sel.first); - atom_it = display_buffer.split(++atom_it, sel.second); - atom_it->attribute() |= Attributes::Underline; - ++atom_it; - ++sel_it; - } - // [------###] - else if (atom.begin() < sel.first and atom.end() > sel.first) - { - atom_it = ++display_buffer.split(atom_it, sel.first); - atom_it->attribute() |= Attributes::Underline; - ++atom_it; - } - // [#########] - else if (atom.begin() >= sel.first and atom.end() <= sel.second) - { - atom_it->attribute() |= Attributes::Underline; - ++atom_it; - } - // [---------] - else if (atom.begin() >= sel.second) - ++sel_it; - // [---------] - else if (atom.end() <= sel.first) - ++atom_it; - else - assert(false); - } - - // invert selection last char for (auto& sel : window.selections()) { + highlight_range(display_buffer, sel.begin(), sel.end(), false, + [](DisplayAtom& atom) { atom.attribute |= Attributes::Underline; }); + const BufferIterator& last = sel.last(); - - DisplayBuffer::iterator atom_it = display_buffer.atom_containing(last); - if (atom_it == display_buffer.end() or not atom_it->splitable()) - continue; - - if (atom_it->begin() < last) - atom_it = ++display_buffer.split(atom_it, last); - if (atom_it->end() > last + 1) - atom_it = display_buffer.split(atom_it, last + 1); - - atom_it->attribute() |= Attributes::Reverse; + highlight_range(display_buffer, last, last+1, false, + [](DisplayAtom& atom) { atom.attribute |= Attributes::Reverse; }); } } - template class SimpleHighlighterFactory { @@ -316,7 +223,7 @@ void register_highlighters() registry.register_factory("highlight_selections", WindowHighlighterFactory("highlight_selections")); registry.register_factory("expand_tabs", WindowHighlighterFactory("expand_tabs")); - registry.register_factory("number_lines", SimpleHighlighterFactory("number_lines")); + registry.register_factory("number_lines", WindowHighlighterFactory("number_lines")); registry.register_factory("regex", colorize_regex_factory); registry.register_factory("group", highlighter_group_factory); } diff --git a/src/ncurses.cc b/src/ncurses.cc index adf2a8f5..cf3b5104 100644 --- a/src/ncurses.cc +++ b/src/ncurses.cc @@ -96,46 +96,30 @@ void NCursesClient::draw_window(Window& window) window.set_dimensions(DisplayCoord(max_y, max_x)); window.update_display_buffer(); - DisplayCoord position; - for (const DisplayAtom& atom : window.display_buffer()) + int line_index = 0; + for (const DisplayLine& line : window.display_buffer().lines()) { - assert(position == atom.coord()); - const String content = atom.content(); - - set_attribute(A_UNDERLINE, atom.attribute() & Underline); - set_attribute(A_REVERSE, atom.attribute() & Reverse); - set_attribute(A_BLINK, atom.attribute() & Blink); - set_attribute(A_BOLD, atom.attribute() & Bold); - - set_color(atom.fg_color(), atom.bg_color()); - - auto pos = content.begin(); - while (true) + move(line_index, 0); + clrtoeol(); + for (const DisplayAtom& atom : line) { - move(position.line, position.column); - clrtoeol(); - auto end = std::find(pos, content.end(), '\n'); - String line(pos, end); - addstr(line.c_str()); + set_attribute(A_UNDERLINE, atom.attribute & Underline); + set_attribute(A_REVERSE, atom.attribute & Reverse); + set_attribute(A_BLINK, atom.attribute & Blink); + set_attribute(A_BOLD, atom.attribute & Bold); - if (end != content.end()) + set_color(atom.fg_color, atom.bg_color); + + String content = atom.content.content(); + if (content[content.length()-1] == '\n') { + addnstr(content.c_str(), content.length() - 1); addch(' '); - position.line = position.line + 1; - position.column = 0; - pos = end + 1; - - if (position.line >= max_y) - break; } else - { - position.column += line.length(); - break; - } + addstr(content.c_str()); } - if (position.line >= max_y) - break; + ++line_index; } set_attribute(A_UNDERLINE, 0); @@ -143,9 +127,9 @@ void NCursesClient::draw_window(Window& window) set_attribute(A_BLINK, 0); set_attribute(A_BOLD, 0); set_color(Color::Blue, Color::Black); - while (++position.line < max_y) + while (++line_index < max_y) { - move(position.line, 0); + move(line_index, 0); clrtoeol(); addch('~'); } diff --git a/src/window.cc b/src/window.cc index 09c5c1d8..c2cf356c 100644 --- a/src/window.cc +++ b/src/window.cc @@ -39,7 +39,8 @@ void Window::update_display_buffer() { scroll_to_keep_cursor_visible_ifn(); - m_display_buffer.clear(); + DisplayBuffer::LineList& lines = m_display_buffer.lines(); + lines.clear(); for (auto line = 0; line < m_dimensions.line; ++line) { @@ -50,16 +51,17 @@ void Window::update_display_buffer() BufferIterator line_begin = buffer().iterator_at_line_begin(pos); BufferIterator line_end = buffer().iterator_at_line_end(pos); - if (line_begin != pos) - { - auto atom_it = m_display_buffer.append(line_begin, pos); - m_display_buffer.replace_atom_content(atom_it, ""); - } - m_display_buffer.append(pos, line_end); + BufferIterator end; + if (line_end - pos > m_dimensions.column) + end = pos + m_dimensions.column; + else + end = line_end; + + lines.push_back(DisplayLine(buffer_line)); + lines.back().push_back(DisplayAtom(AtomContent(pos,end))); } m_highlighters(m_display_buffer); - m_display_buffer.check_invariant(); } void Window::set_dimensions(const DisplayCoord& dimensions)