diff --git a/README.md b/README.md index 9e58cac..ded3fd0 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,12 @@ Just make sure to **NOT COMMIT** these. I suggest using project local vim config Using `:DBUIAddConnection` command or pressing `A` in dbui drawer opens up a prompt to enter database url and name, that will be saved in `g:db_ui_save_location` connections file. These connections are available from everywhere. +To add a connection to a group, prefix the name with a group path, starting with a forward slash: +* `/Production/users` adds a connection named "users" to the "Production" group +* `/Production/EU/orders` adds a connection named "orders" to the "EU" subgroup under "Production" + +Groups can be nested to create hierarchical organization. Non-existent groups will be created automatically. + #### Connection related notes It is possible to have two connections with same name, but from different source. for example, you can have `my-db` in env variable, in `g:dbs` and in saved connections. diff --git a/autoload/db_ui.vim b/autoload/db_ui.vim index e549a93..c384eec 100644 --- a/autoload/db_ui.vim +++ b/autoload/db_ui.vim @@ -163,6 +163,7 @@ endfunction function! s:dbui.new() abort let instance = copy(self) let instance.dbs = {} + let instance.groups = {} let instance.dbs_list = [] let instance.save_path = '' let instance.connections_path = '' @@ -215,16 +216,52 @@ function! s:dbui.populate_dbs() abort call self.populate_from_connections_file() for db in self.dbs_list - let key_name = printf('%s_%s', db.name, db.source) - if !has_key(self.dbs, key_name) || db.url !=? self.dbs[key_name].url - let new_entry = self.generate_new_db_entry(db) - if !empty(new_entry) - let self.dbs[key_name] = new_entry - endif - else - let self.dbs[key_name] = self.drawer.populate(self.dbs[key_name]) + call self.populate_item(db) + endfor + +endfunction + +function! s:dbui.populate_item(item) abort + if self.is_group(a:item) + return self.populate_group(a:item) endif + + return self.populate_db(a:item) +endfunction + +function! s:dbui.populate_group(group) abort + for conn in a:group.connections + call self.populate_item(conn) endfor + + if !has_key(self.groups, a:group.key_name) + let self.groups[a:group.key_name] = self.generate_new_group_entry(a:group) + endif +endfunction + +function! s:dbui.populate_db(db) abort + let key_name = a:db.key_name + + if !has_key(self.dbs, key_name) || a:db.url !=? self.dbs[key_name].url + let new_entry = self.generate_new_db_entry(a:db) + + if !empty(new_entry) + let self.dbs[key_name] = new_entry + endif + + else + let self.dbs[key_name] = self.drawer.populate(self.dbs[key_name]) + endif +endfunction + +function! s:dbui.generate_new_group_entry(db) abort + return { + \ 'name': a:db.name, + \ 'source': a:db.source, + \ 'expanded': 0, + \ 'key_name': a:db.key_name, + \ 'path': a:db.path, + \ } endfunction function! s:dbui.generate_new_db_entry(db) abort @@ -235,7 +272,7 @@ function! s:dbui.generate_new_db_entry(db) abort let db_name = substitute(get(parsed_url, 'path', ''), '^\/', '', '') let save_path = '' if !empty(self.save_path) - let save_path = printf('%s/%s', self.save_path, a:db.name) + let save_path = printf('%s/%s/%s', self.save_path, a:db.path, a:db.name) endif let buffers = filter(copy(self.old_buffers), 'fnamemodify(v:val, ":e") =~? "^".a:db.name."-" || fnamemodify(v:val, ":t") =~? "^".a:db.name."-"') @@ -255,11 +292,12 @@ function! s:dbui.generate_new_db_entry(db) abort \ 'save_path': save_path, \ 'db_name': !empty(db_name) ? db_name : a:db.name, \ 'name': a:db.name, - \ 'key_name': printf('%s_%s', a:db.name, a:db.source), + \ 'key_name': a:db.key_name, \ 'schema_support': 0, \ 'quote': 0, \ 'default_scheme': '', - \ 'filetype': '' + \ 'filetype': '', + \ 'path': a:db.path, \ } call self.populate_schema_info(db) @@ -288,7 +326,7 @@ function! s:dbui.populate_from_global_variable() abort if exists('g:db') && !empty(g:db) let url = self.resolve_url_global_variable(g:db) let gdb_name = split(url, '/')[-1] - call self.add_if_not_exists(gdb_name, url, 'g:dbs') + call self.add_if_not_exists(gdb_name, url, '', 'g:dbs') endif if !exists('g:dbs') || empty(g:dbs) @@ -297,13 +335,13 @@ function! s:dbui.populate_from_global_variable() abort if type(g:dbs) ==? type({}) for [db_name, Db_url] in items(g:dbs) - call self.add_if_not_exists(db_name, self.resolve_url_global_variable(Db_url), 'g:dbs') + call self.add_if_not_exists(db_name, self.resolve_url_global_variable(Db_url), '', 'g:dbs') endfor return self endif for db in g:dbs - call self.add_if_not_exists(db.name, self.resolve_url_global_variable(db.url), 'g:dbs') + call self.add_if_not_exists(db.name, self.resolve_url_global_variable(db.url), '', 'g:dbs') endfor return self @@ -326,7 +364,7 @@ function! s:dbui.populate_from_dotenv() abort for [name, url] in items(all_envs) if stridx(name, prefix) != -1 let db_name = tolower(join(split(name, prefix))) - call self.add_if_not_exists(db_name, url, 'dotenv') + call self.add_if_not_exists(db_name, url, '', 'dotenv') endif endfor endfunction @@ -350,7 +388,7 @@ function! s:dbui.populate_from_env() abort \ printf('Found %s variable for db url, but unable to parse the name. Please provide name via %s', g:db_ui_env_variable_url, g:db_ui_env_variable_name)) endif - call self.add_if_not_exists(env_name, env_url, 'env') + call self.add_if_not_exists(env_name, env_url, '', 'env') return self endfunction @@ -371,22 +409,71 @@ function! s:dbui.populate_from_connections_file() abort let file = db_ui#utils#readfile(self.connections_path) for conn in file - call self.add_if_not_exists(conn.name, conn.url, 'file') + call self.process_connection(conn, []) endfor return self endfunction -function! s:dbui.add_if_not_exists(name, url, source) abort - let existing = get(filter(copy(self.dbs_list), 'v:val.name ==? a:name && v:val.source ==? a:source'), 0, {}) +function! s:dbui.process_connection(conn, parent_groups) abort + if self.is_group(a:conn) + let new_groups = copy(a:parent_groups) + call add(new_groups, a:conn.name) + + for c in a:conn.connections + call self.process_connection(c, new_groups) + endfor + return + endif + + let group_path = join(a:parent_groups, '/') + call self.add_if_not_exists(a:conn.name, a:conn.url, group_path, 'file') +endfunction + +function! s:dbui.add_if_not_exists(name, url, group_path, source) abort + let dbs_list = self.get_target_list(a:group_path, a:source) + let existing = get(filter(copy(dbs_list), 'v:val.name ==? a:name && v:val.source ==? a:source'), 0, {}) + if !empty(existing) return db_ui#notifications#warning(printf('Warning: Duplicate connection name "%s" in "%s" source. First one added has precedence.', a:name, a:source)) endif - return add(self.dbs_list, { - \ 'name': a:name, 'url': db_ui#resolve(a:url), 'source': a:source, 'key_name': printf('%s_%s', a:name, a:source) + + return add(dbs_list, { + \ 'name': a:name, + \ 'url': db_ui#resolve(a:url), + \ 'source': a:source, + \ 'key_name': empty(a:group_path) ? printf('%s_%s', a:name, a:source) : printf('%s/%s_%s', a:group_path, a:name, a:source), + \ 'path': a:group_path, \ }) endfunction +function! s:dbui.get_target_list(group_path, source) abort + let dbs_list = self.dbs_list + + if !empty(a:group_path) + let groups = split(a:group_path, '/') + let current_path = '' + + for group in groups + let existing_group = get(filter(copy(dbs_list), 'v:val.name ==? group && v:val.source ==? a:source'), 0, {}) + if empty(existing_group) + let existing_group = { + \ 'name': group, + \ 'source': a:source, + \ 'key_name': printf('%s/%s_%s', current_path, group, a:source), + \ 'connections': [], + \ 'path': current_path, + \ } + call add(dbs_list, existing_group) + endif + let dbs_list = existing_group.connections + let current_path = printf('%s/%s', current_path, group) + endfor + endif + + return dbs_list +endfunction + function! s:dbui.is_tmp_location_buffer(db, buf) abort if index(a:db.buffers.tmp, a:buf) > -1 return 1 @@ -485,3 +572,11 @@ function! s:get_db(saved_query_db) abort let selected_db = s:dbui_instance.dbs[selected_db.key_name] return selected_db endfunction + +function! s:dbui.is_group(item) abort + return has_key(a:item, 'connections') +endfunction + +function! s:dbui.is_connection(item) abort + return has_key(a:item, 'url') +endfunction diff --git a/autoload/db_ui/connections.vim b/autoload/db_ui/connections.vim index ab06e79..29dcb3a 100644 --- a/autoload/db_ui/connections.vim +++ b/autoload/db_ui/connections.vim @@ -37,7 +37,18 @@ function! s:connections.delete(db) abort endif let file = self.read() - call filter(file, {i, conn -> !(conn.name ==? a:db.name && db_ui#resolve(conn.url) ==? db_ui#resolve(a:db.url) )}) + let target_group = file + + if a:db.path !=? '' + let target_group = self.find_group(a:db.path, file) + endif + + if !has_key(a:db, 'url') + call filter(target_group, {i, conn -> conn.name !=? a:db.name}) + else + call filter(target_group, {i, conn -> !(conn.name ==? a:db.name && db_ui#resolve(conn.url) ==? db_ui#resolve(a:db.url) )}) + endif + return self.write(file) endfunction @@ -62,15 +73,28 @@ function! s:connections.add_full_url() abort return self.save(name, url) endfunction -function! s:connections.rename(db) abort +function! s:connections.enter_db_name(url) abort + let name = db_ui#utils#input('Enter name: ', split(a:url, '/')[-1]) + + if empty(trim(name)) + throw 'Please enter valid name.' + endif + + return name +endfunction + + +function! s:connections.rename_db(db) abort if a:db.source !=? 'file' return db_ui#notifications#error('Cannot edit connections added via variables.') endif - let connections = copy(self.read()) + let connections = self.read() + let target_group = self.find_group(a:db.path, connections) + let idx = 0 let entry = {} - for conn in connections + for conn in target_group if conn.name ==? a:db.name && db_ui#resolve(conn.url) ==? a:db.url let entry = conn break @@ -78,6 +102,10 @@ function! s:connections.rename(db) abort let idx += 1 endfor + if empty(entry) + return db_ui#notifications#error('Database not found.') + endif + let url = entry.url try let url = db_ui#resolve(db_ui#utils#input('Edit connection url for "'.entry.name.'": ', url)) @@ -99,8 +127,82 @@ function! s:connections.rename(db) abort return db_ui#notifications#error(v:exception) endtry - call remove(connections, idx) - let connections = insert(connections, {'name': name, 'url': url }, idx) + call remove(target_group, idx) + call insert(target_group, {'name': name, 'url': url }, idx) + return self.write(connections) +endfunction + +function! s:connections.find_group(path, connections) abort + let target_group = a:connections + for group in split(a:path, '/') + let found = 0 + for conn in target_group + if conn.name ==? group && has_key(conn, 'connections') + let target_group = conn.connections + let found = 1 + break + endif + endfor + if !found + return db_ui#notifications#error('Group not found.') + endif + endfor + return target_group +endfunction + +function! s:connections.get_or_create_group(path, connections) abort + let target_group = a:connections + for group in split(a:path, '/') + let found = 0 + for conn in target_group + if conn.name ==? group && has_key(conn, 'connections') + let target_group = conn.connections + let found = 1 + break + endif + endfor + if !found + call add(target_group, {'name': group, 'connections': []}) + let target_group = target_group[-1].connections + endif + endfor + return target_group +endfunction + +function! s:connections.rename_group(db) abort + if a:db.source !=? 'file' + return db_ui#notifications#error('Cannot edit connections added via variables.') + endif + + let connections = self.read() + let target_group = self.find_group(a:db.path, connections) + + let idx = 0 + let entry = {} + for conn in target_group + if conn.name ==? a:db.name && has_key(conn, 'connections') + let entry = conn + break + endif + let idx += 1 + endfor + + if empty(entry) + return db_ui#notifications#error('Group not found.') + endif + + let name = '' + try + let name = db_ui#utils#input('Edit group name: ', entry.name) + if empty(trim(name)) + throw 'Please enter valid name.' + endif + catch /.*/ + return db_ui#notifications#error(v:exception) + endtry + + call remove(target_group, idx) + call insert(target_group, {'name': name, 'connections': entry.connections}, idx) return self.write(connections) endfunction @@ -119,7 +221,7 @@ function! s:connections.get_file() abort return printf('%s/%s', save_folder, 'connections.json') endfunction -function s:connections.save(name, url) abort +function! s:connections.save(name, url) abort let file = self.get_file() let dir = fnamemodify(file, ':p:h') @@ -131,16 +233,28 @@ function s:connections.save(name, url) abort call writefile(['[]'], file) endif - let file = self.read() - let existing_connection = filter(copy(file), 'v:val.name ==? a:name') + let connections = copy(self.read()) + let target_group = connections + + let db_name = a:name + + if a:name[0] ==? '/' + let path_parts = split(a:name, '/') + let db_name = remove(path_parts, -1) + let target_group = self.get_or_create_group(join(path_parts, '/'), connections) + endif + + let existing_connection = filter(copy(target_group), 'v:val.name ==? db_name') if !empty(existing_connection) call db_ui#notifications#error('Connection with that name already exists. Please enter different name.') return 0 endif - call add(file, {'name': a:name, 'url': a:url}) - return self.write(file) + + call add(target_group, {'name': db_name, 'url': a:url}) + return self.write(connections) endfunction + function! s:connections.read() abort return db_ui#utils#readfile(self.get_file()) endfunction diff --git a/autoload/db_ui/drawer.vim b/autoload/db_ui/drawer.vim index 4f220f0..8440e3f 100644 --- a/autoload/db_ui/drawer.vim +++ b/autoload/db_ui/drawer.vim @@ -250,8 +250,12 @@ function! s:drawer.rename_line() abort return self.rename_buffer(item.file_path, item.dbui_db_key_name, get(item, 'saved', 0)) endif - if item.type ==? 'db' - return self.get_connections().rename(self.dbui.dbs[item.dbui_db_key_name]) + if item.type ==? 'db' + return self.get_connections().rename_db(self.dbui.dbs[item.dbui_db_key_name]) + endif + + if item.type ==? 'group' + return self.get_connections().rename_group(self.dbui.groups[item.dbui_db_key_name]) endif return @@ -331,10 +335,7 @@ function! s:drawer.render(...) abort call self.render_help() for db in self.dbui.dbs_list - if get(opts, 'queries', 0) - call self.load_saved_queries(self.dbui.dbs[db.key_name]) - endif - call self.add_db(self.dbui.dbs[db.key_name]) + call self.add_connection_item(db, opts, 0) endfor if empty(self.dbui.dbs_list) @@ -405,8 +406,38 @@ function! s:drawer.add(label, action, type, icon, dbui_db_key_name, level, ...) call add(self.content, opts) endfunction -function! s:drawer.add_db(db) abort +function! s:drawer.add_connection_item(item, opts, level) abort + if self.dbui.is_group(a:item) + return self.add_group(a:item, a:opts, a:level) + endif + + if get(a:opts, 'queries', 0) + call self.load_saved_queries(self.dbui.dbs[a:item.key_name]) + endif + + call self.add_db(self.dbui.dbs[a:item.key_name], a:level) +endfunction + +function! s:drawer.add_group(group, opts, level) abort + let group = a:group + let opts = a:opts + let level = a:level + + let expanded = get(self.dbui.groups[group.key_name], 'expanded', 0) + + call self.add(group.name, 'toggle', 'group', self.get_toggle_icon('group', self.dbui.groups[group.key_name]), group.key_name, level, { 'expanded': expanded }) + + if expanded + for conn in group.connections + call self.add_connection_item(conn, opts, level + 1) + endfor +endif + +endfunction + +function! s:drawer.add_db(db, level) abort let db_name = a:db.name + let level = a:level if !empty(a:db.conn_error) let db_name .= ' '.g:db_ui_icons.connection_error @@ -418,7 +449,7 @@ function! s:drawer.add_db(db) abort let db_name .= ' ('.a:db.scheme.' - '.a:db.source.')' endif - call self.add(db_name, 'toggle', 'db', self.get_toggle_icon('db', a:db), a:db.key_name, 0, { 'expanded': a:db.expanded }) + call self.add(db_name, 'toggle', 'db', self.get_toggle_icon('db', a:db), a:db.key_name, level, { 'expanded': a:db.expanded }) if !a:db.expanded return a:db endif @@ -426,13 +457,13 @@ function! s:drawer.add_db(db) abort " Render sections based on g:db_ui_drawer_sections configuration for section in g:db_ui_drawer_sections if section ==# 'new_query' - call self._render_new_query_section(a:db) + call self._render_new_query_section(a:db, level+1) elseif section ==# 'buffers' && !empty(a:db.buffers.list) - call self._render_buffers_section(a:db) + call self._render_buffers_section(a:db, level+1) elseif section ==# 'saved_queries' - call self._render_saved_queries_section(a:db) + call self._render_saved_queries_section(a:db, level+1) elseif section ==# 'schemas' - call self._render_schemas_section(a:db) + call self._render_schemas_section(a:db, level+1) endif endfor endfunction @@ -476,6 +507,12 @@ function! s:drawer.toggle_line(edit_action) abort return self.get_query().open(item, a:edit_action) endif + if item.type ==? 'group' + let group = self.dbui.groups[item.dbui_db_key_name] + let group.expanded = !group.expanded + return self.render() + endif + let db = self.dbui.dbs[item.dbui_db_key_name] let tree = db @@ -506,7 +543,7 @@ function! s:drawer.delete_line() abort return endif - if item.action ==? 'toggle' && item.type ==? 'db' + if item.action ==? 'toggle' && item.type ==? 'db' let db = self.dbui.dbs[item.dbui_db_key_name] if db.source !=? 'file' return db_ui#notifications#error('Cannot delete this connection.') @@ -514,6 +551,14 @@ function! s:drawer.delete_line() abort return self.delete_connection(db) endif + if item.type ==? 'group' + let group = self.dbui.groups[item.dbui_db_key_name] + if group.source !=? 'file' + return db_ui#notifications#error('Cannot delete this group.') + endif + return self.get_connections().delete(group) + endif + if item.action !=? 'open' || item.type !=? 'buffer' return endif @@ -698,48 +743,48 @@ function! s:drawer.get_buffer_name(db, buffer) return substitute(name, '^'.db_ui#utils#slug(a:db.name).'-', '', '') endfunction -function! s:drawer._render_new_query_section(db) abort - call self.add('New query', 'open', 'query', g:db_ui_icons.new_query, a:db.key_name, 1) +function! s:drawer._render_new_query_section(db, level) abort + call self.add('New query', 'open', 'query', g:db_ui_icons.new_query, a:db.key_name, a:level) endfunction -function! s:drawer._render_buffers_section(db) abort - call self.add('Buffers ('.len(a:db.buffers.list).')', 'toggle', 'buffers', self.get_toggle_icon('buffers', a:db.buffers), a:db.key_name, 1, { 'expanded': a:db.buffers.expanded }) +function! s:drawer._render_buffers_section(db, level) abort + call self.add('Buffers ('.len(a:db.buffers.list).')', 'toggle', 'buffers', self.get_toggle_icon('buffers', a:db.buffers), a:db.key_name, a:level, { 'expanded': a:db.buffers.expanded }) if a:db.buffers.expanded for buf in a:db.buffers.list let buflabel = self.get_buffer_name(a:db, buf) if self.dbui.is_tmp_location_buffer(a:db, buf) let buflabel .= ' *' endif - call self.add(buflabel, 'open', 'buffer', g:db_ui_icons.buffers, a:db.key_name, 2, { 'file_path': buf }) + call self.add(buflabel, 'open', 'buffer', g:db_ui_icons.buffers, a:db.key_name, a:level+1, { 'file_path': buf }) endfor endif endfunction -function! s:drawer._render_saved_queries_section(db) abort - call self.add('Saved queries ('.len(a:db.saved_queries.list).')', 'toggle', 'saved_queries', self.get_toggle_icon('saved_queries', a:db.saved_queries), a:db.key_name, 1, { 'expanded': a:db.saved_queries.expanded }) +function! s:drawer._render_saved_queries_section(db, level) abort + call self.add('Saved queries ('.len(a:db.saved_queries.list).')', 'toggle', 'saved_queries', self.get_toggle_icon('saved_queries', a:db.saved_queries), a:db.key_name, a:level, { 'expanded': a:db.saved_queries.expanded }) if a:db.saved_queries.expanded for saved_query in a:db.saved_queries.list - call self.add(fnamemodify(saved_query, ':t'), 'open', 'buffer', g:db_ui_icons.saved_query, a:db.key_name, 2, { 'file_path': saved_query, 'saved': 1 }) + call self.add(fnamemodify(saved_query, ':t'), 'open', 'buffer', g:db_ui_icons.saved_query, a:db.key_name, a:level+1, { 'file_path': saved_query, 'saved': 1 }) endfor endif endfunction -function! s:drawer._render_schemas_section(db) abort +function! s:drawer._render_schemas_section(db, level) abort if a:db.schema_support - call self.add('Schemas ('.len(a:db.schemas.items).')', 'toggle', 'schemas', self.get_toggle_icon('schemas', a:db.schemas), a:db.key_name, 1, { 'expanded': a:db.schemas.expanded }) + call self.add('Schemas ('.len(a:db.schemas.items).')', 'toggle', 'schemas', self.get_toggle_icon('schemas', a:db.schemas), a:db.key_name, a:level, { 'expanded': a:db.schemas.expanded }) if a:db.schemas.expanded for schema in a:db.schemas.list let schema_item = a:db.schemas.items[schema] let tables = schema_item.tables - call self.add(schema.' ('.len(tables.items).')', 'toggle', 'schemas->items->'.schema, self.get_toggle_icon('schema', schema_item), a:db.key_name, 2, { 'expanded': schema_item.expanded }) + call self.add(schema.' ('.len(tables.items).')', 'toggle', 'schemas->items->'.schema, self.get_toggle_icon('schema', schema_item), a:db.key_name, a:level+1, { 'expanded': schema_item.expanded }) if schema_item.expanded - call self.render_tables(tables, a:db,'schemas->items->'.schema.'->tables->items', 3, schema) + call self.render_tables(tables, a:db,'schemas->items->'.schema.'->tables->items', a:level+2, schema) endif endfor endif else - call self.add('Tables ('.len(a:db.tables.items).')', 'toggle', 'tables', self.get_toggle_icon('tables', a:db.tables), a:db.key_name, 1, { 'expanded': a:db.tables.expanded }) - call self.render_tables(a:db.tables, a:db, 'tables->items', 2, '') + call self.add('Tables ('.len(a:db.tables.items).')', 'toggle', 'tables', self.get_toggle_icon('tables', a:db.tables), a:db.key_name, a:level, { 'expanded': a:db.tables.expanded }) + call self.render_tables(a:db.tables, a:db, 'tables->items', a:level+1, '') endif endfunction diff --git a/doc/dadbod-ui.txt b/doc/dadbod-ui.txt index 8cd1665..abbad76 100644 --- a/doc/dadbod-ui.txt +++ b/doc/dadbod-ui.txt @@ -233,9 +233,19 @@ Other solution is to have it as a function that resolves a value dynamically. Executing |DBUIAddConnection| opens up a prompt to enter connection that will be saved in a `connections.json` file in |g:db_ui_save_location| folder. These -connections will be available from everywhere. If you want to delete certain -connection, open up DBUI drawer and press `d` (|(DBUI_DeleteLine|) on -the connection you want to delete. +connections will be available from everywhere. + +To add a connection to a group, prefix the connection name with the group path, +starting with a forward slash. For example: +- "/Production/users" adds a connection named "users" to the "Production" group +- "/Production/EU/orders" adds a connection named "orders" to the "EU" subgroup + under "Production" + +If the specified groups don't exist, they will be created automatically. + +If you want to delete a connection or group, open up DBUI drawer and press `d` +(|(DBUI_DeleteLine|) on the connection or group you want to delete. When +deleting a group, all connections within that group will also be deleted. ============================================================================== @@ -688,6 +698,7 @@ g:db_ui_icons \ 'schema': '-', \ 'tables': '-', \ 'table': '-', + \ 'group': '-', \ }, \ 'collapsed': { \ 'db': '+', @@ -697,6 +708,7 @@ g:db_ui_icons \ 'schema': '+', \ 'tables': '+', \ 'table': '+', + \ 'group': '+', \ }, \ } < @@ -711,6 +723,7 @@ g:db_ui_icons 'schema': '▾', 'tables': '▾', 'table': '▾', + 'group': '▾', }, 'collapsed': { 'db': '▸', @@ -720,6 +733,7 @@ g:db_ui_icons 'schema': '▸', 'tables': '▸', 'table': '▸', + 'group': '▸', }, 'saved_query': '*', 'new_query': '+', @@ -745,6 +759,7 @@ g:db_ui_use_nerd_fonts 'schema': '▾ פּ', 'tables': '▾ 藺', 'table': '▾ ', + 'group': '▾ 󰒋', }, 'collapsed': { 'db': '▸ ', @@ -754,6 +769,7 @@ g:db_ui_use_nerd_fonts 'schema': '▸ פּ', 'tables': '▸ 藺', 'table': '▸ ', + 'group': '▸ 󰒋', }, 'saved_query': '', 'new_query': '璘', diff --git a/plugin/db_ui.vim b/plugin/db_ui.vim index dd8fb52..5cfe6ba 100644 --- a/plugin/db_ui.vim +++ b/plugin/db_ui.vim @@ -64,6 +64,7 @@ let g:db_ui_icons = { \ 'schema': s:expanded_icon, \ 'tables': s:expanded_icon, \ 'table': s:expanded_icon, + \ 'group': s:expanded_icon, \ }, \ 'collapsed': { \ 'db': s:collapsed_icon, @@ -73,6 +74,7 @@ let g:db_ui_icons = { \ 'schema': s:collapsed_icon, \ 'tables': s:collapsed_icon, \ 'table': s:collapsed_icon, + \ 'group': s:collapsed_icon, \ }, \ 'saved_query': '*', \ 'new_query': '+', @@ -93,6 +95,7 @@ if g:db_ui_use_nerd_fonts \ 'schema': s:expanded_icon.' 󰙅', \ 'tables': s:expanded_icon.' 󰓱', \ 'table': s:expanded_icon.' ', + \ 'group': s:expanded_icon.' 󰒋', \ }, \ 'collapsed': { \ 'db': s:collapsed_icon.' 󰆼', @@ -102,6 +105,7 @@ if g:db_ui_use_nerd_fonts \ 'schema': s:collapsed_icon.' 󰙅', \ 'tables': s:collapsed_icon.' 󰓱', \ 'table': s:collapsed_icon.' ', + \ 'group': s:collapsed_icon.' 󰒋', \ }, \ 'saved_query': ' ', \ 'new_query': ' 󰓰', diff --git a/test/test-grouped-connections.vim b/test/test-grouped-connections.vim new file mode 100644 index 0000000..2e2478c --- /dev/null +++ b/test/test-grouped-connections.vim @@ -0,0 +1,64 @@ +let s:suite = themis#suite('Grouped Connections') +let s:expect = themis#helper('expect') + +let s:connections_file = g:db_ui_save_location.'/connections.json' + +function s:suite.after() abort + call delete(s:connections_file) + call Cleanup() +endfunction + +function! s:suite.should_create_groups_and_connections() abort + let g:test_connection_name = '/group/connection-first-db' + runtime autoload/db_ui/utils.vim + function! db_ui#utils#input(name, val) + if a:name ==? 'Enter connection url: ' + return 'sqlite:test/dadbod_ui_test.db' + endif + + if a:name ==? 'Enter name: ' + return g:test_connection_name + endif + endfunction + + :DBUI + norm A + call s:expect(getline(1, '$')).to_equal(['▸ group']) + norm o + call s:expect(getline(1, '$')).to_equal(['▾ group', ' ▸ connection-first-db']) + let g:test_connection_name = '/group/nested-group/connection-second-db' + norm A + call s:expect(getline(1, '$')).to_equal(['▾ group', ' ▸ connection-first-db', ' ▸ nested-group']) +endfunction + +function! s:suite.should_allow_renaming_groups_and_connections() abort + let g:test_group_name = 'edited-group-name' + function! db_ui#utils#input(name, val) + if a:name ==? 'Edit group name: ' + return g:test_group_name + endif + + if a:name =~? 'Edit connection url' + return 'sqlite:test/dadbod_ui_test_new.db' + endif + + if a:name ==? 'Edit connection name: ' + return 'edited-db-name' + endif + endfunction + :DBUI + norm r + call s:expect(getline(1, '$')).to_equal(['▸ edited-group-name']) + norm ojr + let g:test_group_name = 'edited-nested-group-name' + norm jr + call s:expect(getline(1, '$')).to_equal(['▾ edited-group-name', ' ▸ edited-db-name', ' ▸ edited-nested-group-name']) +endfunction + +function! s:suite.should_delete_group_and_connection() abort + norm d + call s:expect(getline(1, '$')).to_equal(['▾ edited-group-name', ' ▸ edited-db-name']) + norm d + " Empty group is not shown + call s:expect(getline(1, '$')).to_equal(['" No connections', '[+] Add connection']) +endfunction