This adds a tree view mode to the document list in the sidebar and enables it by default.

Based on previous work by Pavel Roschin <roshin@scriptumplus.ru>, see #259
This commit is contained in:
Thomas Martitz 2022-06-08 14:47:23 +02:00 committed by GitHub
commit def1600a5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 958 additions and 220 deletions

4
NEWS
View File

@ -1,5 +1,7 @@
Geany 1.39 (unreleased)
Interface
* The document list in the sidebar has a new tree view. This mode is the
new default and existing installations automatically use it (PR#1813).
Geany 1.38 (October 09, 2021)

View File

@ -88,24 +88,8 @@ AC_CHECK_DECLS([_NSGetEnviron],,,[[#include <crt_externs.h>]])
GEANY_CHECK_REVISION([dnl force debug mode for a VCS working copy
CFLAGS="-g -DGEANY_DEBUG $CFLAGS"])
# GTK/GLib/GIO checks
gtk_modules="gtk+-3.0 >= 3.0 glib-2.0 >= 2.32"
gtk_modules_private="gio-2.0 >= 2.32 gmodule-no-export-2.0"
PKG_CHECK_MODULES([GTK], [$gtk_modules $gtk_modules_private])
AC_SUBST([DEPENDENCIES], [$gtk_modules])
AS_VAR_APPEND([GTK_CFLAGS], [" -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_32"])
dnl Disable all GTK deprecations
AS_VAR_APPEND([GTK_CFLAGS], [" -DGDK_DISABLE_DEPRECATION_WARNINGS"])
AC_SUBST([GTK_CFLAGS])
AC_SUBST([GTK_LIBS])
GTK_VERSION=`$PKG_CONFIG --modversion gtk+-3.0`
AC_SUBST([GTK_VERSION])
GEANY_STATUS_ADD([Using GTK version], [${GTK_VERSION}])
# GTHREAD checks
gthread_modules="gthread-2.0"
PKG_CHECK_MODULES([GTHREAD], [$gthread_modules])
AC_SUBST([GTHREAD_CFLAGS])
AC_SUBST([GTHREAD_LIBS])
GEANY_CHECK_GTK
GEANY_CHECK_GTK_FUNCS([g_strv_equal])
# --disable-deprecated switch for GTK purification
AC_ARG_ENABLE([deprecated],

View File

@ -23,7 +23,10 @@ dist_htmldocimages_DATA = \
images/pref_dialog_tools.png \
images/pref_dialog_various.png \
images/pref_dialog_vte.png \
images/replace_dialog.png
images/replace_dialog.png \
images/sidebar_documents_only.png \
images/sidebar_show_paths.png \
images/sidebar_show_tree.png
endif
doc_DATA = \

View File

@ -229,7 +229,7 @@ The workspace has the following parts:
* An optional toolbar.
* An optional sidebar that can show the following tabs:
* Documents - A document list, and
* Documents - A document list, see `Document list views`_.
* Symbols - A list of symbols in your code.
* The main editor window.
@ -513,6 +513,48 @@ order. It is not alphabetical as shown in the documents list
See the `Notebook tab keybindings`_ section for useful
shortcuts including for Most-Recently-Used document switching.
Document list views
^^^^^^^^^^^^^^^^^^^
There are three different ways to display documents on the sidebar if *Show
documents list* is active. To switch between views press the right mouse button
on the documents list and select one of these items:
Documents Only
Show only file names of open documents in sorted order.
.. image:: ./images/sidebar_documents_only.png
Show Paths
Show open documents as a two-level tree in which first level is the paths
of directories containing open files and the second level is the file names of
the documents open in that path. All documents with the same path are grouped
together under the same first level item. Paths are in sorted order and
documents are sorted within each group.
.. image:: ./images/sidebar_show_paths.png
Show Tree
Show paths as above, but as a multiple level partial tree. The tree is only
expanded at positions where two or more directory paths to open documents
share the same prefix. The common prefix is shown as a parent level, and
the remainder of those paths are shown as child levels. This applies
recursively down the paths making a tree to the file names of open documents,
which are grouped in sorted order as an additional level below the last path
segment.
For convenience two common file locations are handled specially, open
files below the users home directory and open files below an open project
base path. Each of these is moved to its own top level tree instead of
being in place in the normal tree. The top level of these trees are each
labelled differently. For the home directory tree the path of the home
directory is shown as ``~``, and for the project tree the path to the project
base path is shown simply as the project name.
.. image:: ./images/sidebar_show_tree.png
In all cases paths and file names that do not fit in the width available are ellipsised.
Cloning documents
^^^^^^^^^^^^^^^^^
The `Document->Clone` menu item copies the current document's text,

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -39,6 +39,9 @@ if tarball.returncode() == 0 or rst2html.found()
'images/pref_dialog_various.png',
'images/pref_dialog_vte.png',
'images/replace_dialog.png',
'images/sidebar_documents_only.png',
'images/sidebar_show_paths.png',
'images/sidebar_show_tree.png',
install_dir: join_paths(cdata.get('GEANY_DOC_DIR'), 'html', 'images')
)
if tarball.returncode() == 0

33
m4/geany-gtk.m4 Normal file
View File

@ -0,0 +1,33 @@
dnl GEANY_CHECK_GTK
dnl Checks whether the GTK stack is available and new enough. Sets GTK_CFLAGS and GTK_LIBS.
AC_DEFUN([GEANY_CHECK_GTK],
[
gtk_modules="gtk+-3.0 >= 3.0 glib-2.0 >= 2.32"
gtk_modules_private="gio-2.0 >= 2.32 gmodule-no-export-2.0 gthread-2.0"
PKG_CHECK_MODULES([GTK], [$gtk_modules $gtk_modules_private])
AC_SUBST([DEPENDENCIES], [$gtk_modules])
AS_VAR_APPEND([GTK_CFLAGS], [" -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_32"])
dnl Disable all GTK deprecations
AS_VAR_APPEND([GTK_CFLAGS], [" -DGDK_DISABLE_DEPRECATION_WARNINGS"])
AC_SUBST([GTK_CFLAGS])
AC_SUBST([GTK_LIBS])
AC_SUBST([GTK_VERSION],[`$PKG_CONFIG --modversion gtk+-3.0`])
GEANY_STATUS_ADD([Using GTK version], [${GTK_VERSION}])
])
dnl GEANY_CHECK_GTK_FUNCS
dnl Like AC_CHECK_FUNCS but adds GTK flags so that tests for GLib/GTK functions may succeed.
AC_DEFUN([GEANY_CHECK_GTK_FUNCS],
[
AC_REQUIRE([GEANY_CHECK_GTK])
CFLAGS_save=$CFLAGS
CFLAGS=$GTK_CFLAGS
LIBS_save=$LIBS
LIBS=$GTK_LIBS
AC_CHECK_FUNCS([$1])
CFLAGS=$CFLAGS_save
LIBS=$LIBS_save
])

View File

@ -87,7 +87,7 @@ check_functions = [
['truncate', '#include <unistd.h>'],
['wcrtomb', '#include <wchar.h>'],
['wcscoll', '#include <wchar.h>'],
['g_strv_equal', '#include <glib.h>']
]
foreach h : check_headers
@ -102,7 +102,7 @@ endforeach
foreach f : check_functions
define = 'HAVE_' + f.get(0).underscorify().to_upper()
ccprefix = '\n'.join([gnu_source ? '#define _GNU_SOURCE' : '', f.get(1)])
if cc.has_function(f.get(0), prefix : ccprefix)
if cc.has_function(f.get(0), prefix: ccprefix, dependencies: deps)
cdata.set(define, 1)
else
cdata.set(define, false)
@ -857,7 +857,7 @@ libgeany = shared_library('geany',
)
dep_libgeany = declare_dependency(
link_with: libgeany,
include_directories: [igeany]
include_directories: [iscintilla, itagmanager, igeany]
)
executable('geany',

View File

@ -20,14 +20,14 @@ AM_CPPFLAGS = \
-DGTK \
-DGEANY_PRIVATE \
-DG_LOG_DOMAIN=\""Geany"\" \
@GTK_CFLAGS@ @GTHREAD_CFLAGS@ \
@GTK_CFLAGS@ \
$(MAC_INTEGRATION_CFLAGS)
bin_PROGRAMS = geany
lib_LTLIBRARIES = libgeany.la
geany_SOURCES = main.c
geany_LDADD = libgeany.la $(GTK_LIBS) $(GTHREAD_LIBS) $(INTLLIBS)
geany_LDADD = libgeany.la $(GTK_LIBS) $(INTLLIBS)
geany_LDFLAGS =
if ENABLE_BINRELOC
@ -124,7 +124,6 @@ libgeany_la_LIBADD = \
$(top_builddir)/scintilla/libscintilla.la \
$(builddir)/tagmanager/libtagmanager.la \
@GTK_LIBS@ \
@GTHREAD_LIBS@ \
$(MAC_INTEGRATION_LIBS) \
$(INTLLIBS)

View File

@ -187,7 +187,7 @@ static void init_pref_groups(void)
stash_group_add_toggle_button(group, &file_prefs.tab_close_switch_to_mru,
"tab_close_switch_to_mru", FALSE, "check_tab_close_switch_to_mru");
stash_group_add_integer(group, &interface_prefs.tab_pos_sidebar, "tab_pos_sidebar", GTK_POS_TOP);
stash_group_add_integer(group, &interface_prefs.documents_show_paths, "documents_show_paths", SHOW_PATHS_LIST);
stash_group_add_integer(group, &interface_prefs.openfiles_path_mode, "openfiles_path_mode", -1);
stash_group_add_radio_buttons(group, &interface_prefs.sidebar_pos,
"sidebar_pos", GTK_POS_LEFT,
"radio_sidebar_left", GTK_POS_LEFT,

View File

@ -1018,20 +1018,12 @@ static const gchar *get_locale(void)
GEANY_EXPORT_SYMBOL
gint main_lib(gint argc, gchar **argv)
void main_init_headless(void)
{
GeanyDocument *doc;
gint config_dir_result;
const gchar *locale;
gchar *utf8_configdir;
gchar *os_info;
#if ! GLIB_CHECK_VERSION(2, 36, 0)
g_type_init();
#endif
log_handlers_init();
app = g_new0(GeanyApp, 1);
memset(&main_status, 0, sizeof(GeanyStatus));
memset(&prefs, 0, sizeof(GeanyPrefs));
@ -1043,6 +1035,21 @@ gint main_lib(gint argc, gchar **argv)
memset(&template_prefs, 0, sizeof(GeanyTemplatePrefs));
memset(&ui_prefs, 0, sizeof(UIPrefs));
memset(&ui_widgets, 0, sizeof(UIWidgets));
}
GEANY_EXPORT_SYMBOL
gint main_lib(gint argc, gchar **argv)
{
GeanyDocument *doc;
gint config_dir_result;
const gchar *locale;
gchar *utf8_configdir;
gchar *os_info;
main_init_headless();
log_handlers_init();
setup_paths();

View File

@ -76,6 +76,8 @@ void main_load_project_from_command_line(const gchar *locale_filename, gboolean
gint main_lib(gint argc, gchar **argv);
void main_init_headless(void);
#endif /* GEANY_PRIVATE */
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@ -27,6 +27,8 @@
#include "gtkcompat.h"
#ifdef GEANY_PRIVATE
G_BEGIN_DECLS
typedef struct SidebarTreeviews
@ -50,8 +52,21 @@ enum
enum
{
SHOW_PATHS_NONE,
SHOW_PATHS_LIST
OPENFILES_PATHS_NONE,
OPENFILES_PATHS_LIST,
OPENFILES_PATHS_TREE,
OPENFILES_PATHS_COUNT
};
/* documents tree model columns */
enum
{
DOCUMENTS_ICON,
DOCUMENTS_SHORTNAME, /* dirname for parents, basename for children */
DOCUMENTS_DOCUMENT,
DOCUMENTS_COLOR,
DOCUMENTS_FILENAME, /* full filename */
DOCUMENTS_FOLD, /* fold state stored when folding parent rows */
};
void sidebar_init(void);
@ -76,6 +91,10 @@ void sidebar_focus_openfiles_tab(void);
void sidebar_focus_symbols_tab(void);
GtkTreeStore *sidebar_create_store_openfiles(void);
#endif
G_END_DECLS
#endif /* GEANY_SIDEBAR_H */

View File

@ -68,7 +68,7 @@
"filetype: %f " \
"scope: %S")
GeanyInterfacePrefs interface_prefs;
GEANY_EXPORT_SYMBOL GeanyInterfacePrefs interface_prefs;
GeanyMainWidgets main_widgets;
UIPrefs ui_prefs;

View File

@ -71,7 +71,7 @@ typedef struct GeanyInterfacePrefs
gint symbols_sort_mode; /**< symbol list sorting mode */
/** whether to show a warning when closing a project to open a new one */
gboolean warn_on_project_close;
gint documents_show_paths;
gint openfiles_path_mode;
}
GeanyInterfacePrefs;

View File

@ -468,7 +468,7 @@ gdouble utils_scale_round(gdouble val, gdouble factor)
/* like g_utf8_strdown() but if @str is not valid UTF8, convert it from locale first.
* returns NULL on charset conversion failure */
static gchar *utf8_strdown(const gchar *str)
gchar *utils_utf8_strdown(const gchar *str)
{
gchar *down;
@ -512,10 +512,10 @@ gint utils_str_casecmp(const gchar *s1, const gchar *s2)
g_return_val_if_fail(s2 != NULL, -1);
/* ensure strings are UTF-8 and lowercase */
tmp1 = utf8_strdown(s1);
tmp1 = utils_utf8_strdown(s1);
if (! tmp1)
return 1;
tmp2 = utf8_strdown(s2);
tmp2 = utils_utf8_strdown(s2);
if (! tmp2)
{
g_free(tmp1);

View File

@ -194,6 +194,8 @@ gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFl
GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
GError **error);
gchar *utils_utf8_strdown(const gchar *str);
gint utils_str_casecmp(const gchar *s1, const gchar *s2);
gchar *utils_get_date_time(const gchar *format, time_t *time_to_use);

View File

@ -1,13 +1,15 @@
SUBDIRS = ctags
AM_CPPFLAGS = -DGEANY_PRIVATE -DG_LOG_DOMAIN=\""Geany"\" @GTK_CFLAGS@ @GTHREAD_CFLAGS@
AM_CPPFLAGS += -I$(top_srcdir)/src
AM_CPPFLAGS = -DGTK -DGEANY_PRIVATE -DG_LOG_DOMAIN=\""Geany"\"
AM_CPPFLAGS += -I$(top_srcdir)/scintilla/include -I$(top_srcdir)/scintilla/lexilla/include
AM_CPPFLAGS += -I$(top_srcdir)/src/tagmanager -I$(top_srcdir)/src
AM_CFLAGS = $(GTK_CFLAGS)
AM_LDFLAGS = $(GTK_LIBS) $(INTLLIBS) -no-install
AM_LDFLAGS = $(GTK_LIBS) $(GTHREAD_LIBS) $(INTLLIBS) -no-install
check_PROGRAMS = test_utils
check_PROGRAMS = test_utils test_sidebar
test_utils_LDADD = $(top_builddir)/src/libgeany.la
test_sidebar_LDADD = $(top_builddir)/src/libgeany.la
TESTS = $(check_PROGRAMS)

View File

@ -1,5 +1,6 @@
test_deps = declare_dependency(compile_args: geany_cflags + [ '-DG_LOG_DOMAIN="Geany"' ],
dependencies: [deps, dep_libgeany])
dependencies: [deps, dep_libgeany],
include_directories: '..')
ctags_tests = files([
'ctags/1795612.js.tags',
@ -366,3 +367,4 @@ test('ctags/processing-order', runner,
args: [join_paths(meson.build_root(), 'geany'), '--result', process_order_sources],
env: ['top_srcdir='+meson.source_root(), 'top_builddir='+meson.build_root()])
test('utils', executable('test_utils', 'test_utils.c', dependencies: test_deps))
test('sidebar', executable('test_sidebar', 'test_sidebar.c', dependencies: test_deps))

154
tests/test_sidebar.c Normal file
View File

@ -0,0 +1,154 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "document.h"
#include "documentprivate.h"
#include "keyfile.h"
#include "main.h"
#include "sidebar.h"
#include "ui_utils.h"
#include "utils.h"
#define SIDEBAR_TEST_ADD(path, func) g_test_add_func("/sidebar/" path, func);
static void openfiles_add(const gchar **file_names)
{
const gchar *file;
while ((file = *file_names++))
{
GeanyDocument *doc = g_new0(GeanyDocument, 1);
doc->priv = g_new0(GeanyDocumentPrivate, 1);
doc->file_name = strdup(file);
sidebar_openfiles_add(doc);
}
}
static gboolean tree_count_cb(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data_in)
{
gint *c = (gint *) data_in;
*c = *c + 1;
return FALSE;
}
static gboolean tree_strings_cb(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data_in)
{
gchar **data = (gchar **) data_in;
gchar *file;
gtk_tree_model_get(model, iter, DOCUMENTS_SHORTNAME, &file, -1);
data[g_strv_length(data)] = file;
printf("%s\n", file);
return FALSE;
}
void do_test_sidebar_openfiles(const gchar **test_data, const gchar **expected)
{
#ifdef HAVE_G_STRV_EQUAL
int count = 0;
GtkTreeStore *store;
gchar **data;
store = sidebar_create_store_openfiles();
openfiles_add(test_data);
gtk_tree_model_foreach(GTK_TREE_MODEL(store), tree_count_cb, (gpointer) &count);
data = g_new0(gchar *, count + 1);
gtk_tree_model_foreach(GTK_TREE_MODEL(store), tree_strings_cb, (gpointer) data);
g_assert_true(g_strv_equal(expected, (const gchar * const *) data));
#else
g_test_skip("Need g_strv_equal(), since GLib 2.60");
#endif
}
void test_sidebar_openfiles_none(void)
{
const gchar *files[] = {
"/tmp/x",
"/tmp/b/a",
"/tmp/b/b",
NULL
};
const gchar *expected[] = {
"a",
"b",
"x",
NULL
};
interface_prefs.openfiles_path_mode = OPENFILES_PATHS_NONE;
do_test_sidebar_openfiles(files, expected);
}
void test_sidebar_openfiles_path(void)
{
const gchar *files[] = {
"/tmp/x",
"/tmp/b/a",
"/tmp/b/b",
NULL
};
const gchar *expected[] = {
"/tmp",
"x",
"/tmp/b",
"a",
"b",
NULL
};
interface_prefs.openfiles_path_mode = OPENFILES_PATHS_LIST;
do_test_sidebar_openfiles(files, expected);
}
void test_sidebar_openfiles_tree(void)
{
const gchar *files[] = {
"/tmp/x",
"/tmp/b/a",
"/tmp/b/b",
NULL
};
const gchar *expected[] = {
"/tmp",
"x",
"b",
"a",
"b",
NULL
};
interface_prefs.openfiles_path_mode = OPENFILES_PATHS_TREE;
do_test_sidebar_openfiles(files, expected);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
/* Not sure if we can really continue without DISPLAY. Fake X display perhaps?
*
* This test seems to work, at least.
*/
gtk_init_check(&argc, &argv);
main_init_headless();
SIDEBAR_TEST_ADD("openfiles_none", test_sidebar_openfiles_none);
SIDEBAR_TEST_ADD("openfiles_path", test_sidebar_openfiles_path);
SIDEBAR_TEST_ADD("openfiles_tree", test_sidebar_openfiles_tree);
return g_test_run();
}