From 77577bf205d24d84aa8e14318a9aaf7edee6472f Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 11 Mar 2010 13:37:37 +0300 Subject: [PATCH 01/71] File system events notifier for Linux --- native/fsNotifier/linux/fsnotifier | Bin 0 -> 23388 bytes native/fsNotifier/linux/fsnotifier.h | 68 +++++ native/fsNotifier/linux/inotify.c | 376 +++++++++++++++++++++++++++ native/fsNotifier/linux/main.c | 326 +++++++++++++++++++++++ native/fsNotifier/linux/make.sh | 1 + native/fsNotifier/linux/util.c | 190 ++++++++++++++ 6 files changed, 961 insertions(+) create mode 100755 native/fsNotifier/linux/fsnotifier create mode 100644 native/fsNotifier/linux/fsnotifier.h create mode 100644 native/fsNotifier/linux/inotify.c create mode 100644 native/fsNotifier/linux/main.c create mode 100755 native/fsNotifier/linux/make.sh create mode 100644 native/fsNotifier/linux/util.c diff --git a/native/fsNotifier/linux/fsnotifier b/native/fsNotifier/linux/fsnotifier new file mode 100755 index 0000000000000000000000000000000000000000..3651a1326a35eeff6a4c0c144ab6b70d01a67095 GIT binary patch literal 23388 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 literal 0 HcmV?d00001 diff --git a/native/fsNotifier/linux/fsnotifier.h b/native/fsNotifier/linux/fsnotifier.h new file mode 100644 index 000000000000..1023f183bf19 --- /dev/null +++ b/native/fsNotifier/linux/fsnotifier.h @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FSNOTIFIER_H +#define __FSNOTIFIER_H + +#include +#include + + +// variable-length array +typedef struct __array array; + +array* array_create(int initial_capacity); +int array_size(array* a); +void* array_push(array* a, void* element); +void* array_pop(array* a); +void array_put(array* a, int index, void* element); +void* array_get(array* a, int index); +void array_delete(array* a); +void array_delete_vs_data(array* a); + + +// key/value pairs table +typedef struct __table table; + +table* table_create(int capacity); +void* table_put(table* t, int key, void* value); +void* table_get(table* t, int key); +void table_delete(table* t); + + +// inotify subsystem +enum { + ERR_IGNORE = -1, + ERR_CONTINUE = -2, + ERR_ABORT = -3 +}; + +bool init_inotify(); +void set_inotify_callback(void (* callback)(char*, int)); +int get_inotify_fd(); +int get_watch_count(); +bool watch_limit_reached(); +int watch(const char* root, array* ignores); +void unwatch(int id); +bool process_inotify_input(); +void close_inotify(); + + +// reads one line from stream, trims trailing carriage return if any +// returns pointer to the internal buffer (will be overwriten on next call) +char* read_line(FILE* stream); + +#endif diff --git a/native/fsNotifier/linux/inotify.c b/native/fsNotifier/linux/inotify.c new file mode 100644 index 000000000000..ae799e262489 --- /dev/null +++ b/native/fsNotifier/linux/inotify.c @@ -0,0 +1,376 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fsnotifier.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define WATCH_COUNT_NAME "/proc/sys/fs/inotify/max_user_watches" + +#define DEFAULT_SUBDIR_COUNT 5 + +#define CHECK_NULL(p) if (p == NULL) { syslog(LOG_ERR, "out of memory"); return ERR_ABORT; } + +typedef struct __watch_node { + char* name; + int wd; + struct __watch_node* parent; + array* kids; +} watch_node; + +static int inotify_fd = -1; +static int watch_count = 0; +static table* watches; +static bool limit_reached = false; +static void (* callback)(char*, int) = NULL; + +#define EVENT_SIZE (sizeof(struct inotify_event)) +#define EVENT_BUF_LEN (2048 * (EVENT_SIZE + 16)) +static char event_buf[EVENT_BUF_LEN]; + + +static void read_watch_descriptors_count() { + FILE* f = fopen(WATCH_COUNT_NAME, "r"); + if (f == NULL) { + syslog(LOG_ERR, "can't open %s: %s", WATCH_COUNT_NAME, strerror(errno)); + return; + } + + char* str = read_line(f); + if (str == NULL) { + syslog(LOG_ERR, "can't read from %s", WATCH_COUNT_NAME); + } + else { + watch_count = atoi(str); + } + + fclose(f); +} + + +bool init_inotify() { + inotify_fd = inotify_init(); + if (inotify_fd < 0) { + syslog(LOG_ERR, "inotify_init: %s", strerror(errno)); + return false; + } + syslog(LOG_DEBUG, "inotify fd: %d", get_inotify_fd()); + + read_watch_descriptors_count(); + if (watch_count <= 0) { + close(inotify_fd); + inotify_fd = -1; + return false; + } + syslog(LOG_INFO, "inotify watch descriptors: %d", watch_count); + + watches = table_create(watch_count); + if (watches == NULL) { + syslog(LOG_ERR, "out of memory"); + close(inotify_fd); + inotify_fd = -1; + return false; + } + + return true; +} + + +inline void set_inotify_callback(void (* _callback)(char*, int)) { + callback = _callback; +} + + +inline int get_inotify_fd() { + return inotify_fd; +} + + +inline int get_watch_count() { + return watch_count; +} + + +inline bool watch_limit_reached() { + return limit_reached; +} + + +static int add_watch(const char* path, watch_node* parent) { + int wd = inotify_add_watch(inotify_fd, path, IN_MODIFY | IN_ATTRIB | IN_CREATE | IN_DELETE | IN_MOVE | IN_DELETE_SELF); + if (wd < 0) { + if (errno == ENOSPC) { + limit_reached = true; + } + syslog(LOG_ERR, "inotify_add_watch(%s): %s", path, strerror(errno)); + return ERR_CONTINUE; + } + else { + syslog(LOG_DEBUG, "watching %s: %d", path, wd); + } + + watch_node* node = table_get(watches, wd); + if (node != NULL) { + if (node->wd != wd || strcmp(node->name, path) != 0) { + syslog(LOG_ERR, "table error: collision (new %d:%s, existing %d:%s)", wd, path, node->wd, node->name); + return ERR_ABORT; + } + + return wd; + } + + node = malloc(sizeof(watch_node)); + + CHECK_NULL(node); + node->name = strdup(path); + CHECK_NULL(node->name); + node->wd = wd; + node->parent = parent; + node->kids = NULL; + + if (parent != NULL) { + if (parent->kids == NULL) { + parent->kids = array_create(DEFAULT_SUBDIR_COUNT); + CHECK_NULL(parent->kids); + } + CHECK_NULL(array_push(parent->kids, node)); + } + + if (table_put(watches, wd, node) == NULL) { + syslog(LOG_ERR, "table error: unable to put (%d:%s)", wd, path); + return ERR_ABORT; + } + + return wd; +} + + +static void rm_watch(int wd, bool update_parent) { + watch_node* node = table_get(watches, wd); + if (node == NULL) { + return; + } + + syslog(LOG_DEBUG, "unwatching %s: %d (%p)", node->name, node->wd, node); + + if (inotify_rm_watch(inotify_fd, node->wd) < 0) { + syslog(LOG_DEBUG, "inotify_rm_watch(%d:%s): %s", node->wd, node->name, strerror(errno)); + } + + for (int i=0; ikids); i++) { + watch_node* kid = array_get(node->kids, i); + if (kid != NULL) { + rm_watch(kid->wd, false); + } + } + + if (update_parent && node->parent != NULL) { + for (int i=0; iparent->kids); i++) { + if (array_get(node->parent->kids, i) == node) { + array_put(node->parent->kids, i, NULL); + break; + } + } + } + + free(node->name); + array_delete(node->kids); + free(node); + table_put(watches, wd, NULL); +} + + +static bool is_directory(struct dirent* entry, const char* path) { + if (entry->d_type == DT_DIR) { + return true; + } + else if (entry->d_type == DT_UNKNOWN) { // filesystem doesn't support d_type + struct stat st; + return (stat(path, &st) == 0 && S_ISDIR(st.st_mode)); + } + return false; +} + +static bool is_ignored(const char* path, array* ignores) { + if (ignores != NULL) { + int pl = strlen(path); + for (int i=0; i= il && strncmp(path, ignore, il) == 0) { + syslog(LOG_DEBUG, "path %s is under unwatchable %s - ignoring", path, ignore); + return true; + } + } + } + return false; +} + +static int walk_tree(const char* path, watch_node* parent, array* ignores) { + if (is_ignored(path, ignores)) { + return ERR_IGNORE; + } + + DIR* dir = opendir(path); + if (dir == NULL) { + if (errno == EACCES) { + return ERR_IGNORE; + } + else if (errno == ENOTDIR) { // flat root + return add_watch(path, parent); + } + syslog(LOG_ERR, "opendir(%s): %s", path, strerror(errno)); + return ERR_CONTINUE; + } + + int id = add_watch(path, parent); + if (id < 0) { + closedir(dir); + return id; + } + + struct dirent* entry; + char subdir[PATH_MAX]; + strcpy(subdir, path); + if (subdir[strlen(subdir) - 1] != '/') { + strcat(subdir, "/"); + } + char* p = subdir + strlen(subdir); + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + strcpy(p, entry->d_name); + if (!is_directory(entry, subdir)) { + continue; + } + + int subdir_id = walk_tree(subdir, table_get(watches, id), ignores); + if (subdir_id < 0 && subdir_id != ERR_IGNORE) { + rm_watch(id, true); + id = subdir_id; + break; + } + } + + closedir(dir); + return id; +} + + +int watch(const char* root, array* ignores) { + return walk_tree(root, NULL, ignores); +} + + +void unwatch(int id) { + rm_watch(id, true); +} + + +static bool process_inotify_event(struct inotify_event* event) { + watch_node* node = table_get(watches, event->wd); + if (node == NULL) { + return true; + } + + syslog(LOG_DEBUG, "inotify: wd=%d mask=%d dir=%d name=%s", + event->wd, event->mask & (~IN_ISDIR), (event->mask & IN_ISDIR) != 0, node->name); + + char path[PATH_MAX]; + strcpy(path, node->name); + if (event->len > 0) { + if (path[strlen(path) - 1] != '/') { + strcat(path, "/"); + } + strcat(path, event->name); + } + + if ((event->mask & IN_CREATE || event->mask & IN_MOVED_TO) && event->mask & IN_ISDIR) { + int result = walk_tree(path, node, NULL); + if (result < 0 && result != ERR_IGNORE) { + return false; + } + } + + if ((event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) && event->mask & IN_ISDIR) { + for (int i=0; ikids); i++) { + watch_node* kid = array_get(node->kids, i); + if (kid != NULL && strcmp(kid->name, path) == 0) { + rm_watch(kid->wd, false); + array_put(node->kids, i, NULL); + break; + } + } + } + + if (callback != NULL) { + (*callback)(path, event->mask); + } + return true; +} + + +bool process_inotify_input() { + size_t len = read(inotify_fd, event_buf, EVENT_BUF_LEN); + if (len < 0) { + syslog(LOG_ERR, "read: %s", strerror(errno)); + return false; + } + + int i = 0; + while (i < len) { + struct inotify_event* event = (struct inotify_event*) &event_buf[i]; + i += EVENT_SIZE + event->len; + + if (event->mask & IN_IGNORED) { + continue; + } + if (event->mask & IN_Q_OVERFLOW) { + syslog(LOG_ERR, "event queue overflow"); + continue; + } + + if (!process_inotify_event(event)) { + return false; + } + } + + return true; +} + + +void close_inotify() { + if (watches != NULL) { + table_delete(watches); + } + + if (inotify_fd >= 0) { + close(inotify_fd); + } +} diff --git a/native/fsNotifier/linux/main.c b/native/fsNotifier/linux/main.c new file mode 100644 index 000000000000..ba498a7913bd --- /dev/null +++ b/native/fsNotifier/linux/main.c @@ -0,0 +1,326 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fsnotifier.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_ENV "FSNOTIFIER_LOG_LEVEL" +#define LOG_ENV_DEBUG "debug" +#define LOG_ENV_INFO "info" +#define LOG_ENV_WARNING "warning" +#define LOG_ENV_ERROR "error" +#define LOG_ENV_OFF "off" + +#define USAGE_MSG \ + "fsnotifier - IntelliJ IDEA companion program for watching and reporting file and directory structure modifications.\n\n" \ + "fsnotifier utilizes \"user\" facility of syslog(3) - messages usually can be found in /var/log/user.log.\n" \ + "Verbosity is regulated via " LOG_ENV " environment variable, possible values are: " \ + LOG_ENV_DEBUG ", " LOG_ENV_INFO ", " LOG_ENV_WARNING ", " LOG_ENV_ERROR ", " LOG_ENV_OFF "; latter is the default.\n" + +#define INOTIFY_LIMIT_MSG \ + "The current inotify(7) watch limit of %d is too low. " \ + "More details.\n" + +typedef struct { + char* name; + int id; +} watch_root; + +static array* roots = NULL; + +static bool show_warning = true; + +#define CHECK_NULL(p) if (p == NULL) { syslog(LOG_ERR, "out of memory"); return false; } + +static void init_log(); +static void main_loop(); +static bool read_input(); +static bool update_roots(array* new_roots); +static void unregister_roots(); +static bool register_roots(array* new_roots, array* unwatchable); +static bool unwatchable_mounts(array* mounts); +static void inotify_callback(char* path, int event); + + +int main(int argc, char** argv) { + if (argc == 2 && strcmp(argv[1], "--help") == 0) { + printf(USAGE_MSG); + return 0; + } + + init_log(); + syslog(LOG_INFO, "started"); + + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + + roots = array_create(20); + if (init_inotify() && roots != NULL) { + set_inotify_callback(&inotify_callback); + main_loop(); + unregister_roots(); + } + else { + printf("GIVEUP\n"); + } + close_inotify(); + array_delete(roots); + + syslog(LOG_INFO, "finished"); + closelog(); + + return 0; +} + + +static void init_log() { + char* env_level = getenv(LOG_ENV); + int level = LOG_EMERG; + if (env_level != NULL) { + if (strcmp(env_level, LOG_ENV_DEBUG) == 0) level = LOG_DEBUG; + else if (strcmp(env_level, LOG_ENV_INFO) == 0) level = LOG_INFO; + else if (strcmp(env_level, LOG_ENV_WARNING) == 0) level = LOG_WARNING; + else if (strcmp(env_level, LOG_ENV_ERROR) == 0) level = LOG_ERR; + } + + char ident[32]; + snprintf(ident, sizeof(ident), "fsnotifier[%d]", getpid()); + openlog(ident, 0, LOG_USER); + setlogmask(LOG_UPTO(level)); +} + + +static void main_loop() { + int input_fd = fileno(stdin), inotify_fd = get_inotify_fd(); + int nfds = (inotify_fd > input_fd ? inotify_fd : input_fd) + 1; + fd_set rfds; + bool go_on = true; + + while (go_on) { + FD_ZERO(&rfds); + FD_SET(input_fd, &rfds); + FD_SET(inotify_fd, &rfds); + if (select(nfds, &rfds, NULL, NULL, NULL) < 0) { + syslog(LOG_ERR, "select: %s", strerror(errno)); + go_on = false; + } + else if (FD_ISSET(input_fd, &rfds)) { + go_on = read_input(); + } + else if (FD_ISSET(inotify_fd, &rfds)) { + go_on = process_inotify_input(); + } + } +} + + +static bool read_input() { + char* line = read_line(stdin); + syslog(LOG_DEBUG, "input: %s", (line ? line : "")); + + if (line == NULL || strcmp(line, "EXIT") == 0) { + return false; + } + + if (strcmp(line, "ROOTS") == 0) { + array* new_roots = array_create(20); + CHECK_NULL(new_roots); + + while (1) { + line = read_line(stdin); + syslog(LOG_DEBUG, "input: %s", (line ? line : "")); + if (line == NULL || strlen(line) == 0) { + return false; + } + else if (strcmp(line, "#") == 0) { + break; + } + else { + if (line[0] == '|') line++; // flat roots will be differentiated later + + int l = strlen(line); + if (l > 1 && line[l-1] == '/') line[l-1] = '\0'; + + CHECK_NULL(array_push(new_roots, strdup(line))); + } + } + + return update_roots(new_roots); + } + + return true; +} + + +static bool update_roots(array* new_roots) { + syslog(LOG_INFO, "updating roots (curr:%d, new:%d)", array_size(roots), array_size(new_roots)); + + unregister_roots(); + if (array_size(new_roots) == 0) { + return true; + } + else if (array_size(new_roots) == 1 && strcmp(array_get(new_roots, 0), "/") == 0) { // refuse to watch entire tree + printf("UNWATCHEABLE\n/\n#\n"); + syslog(LOG_INFO, "unwatchable: /"); + array_delete_vs_data(new_roots); + return true; + } + + array* unwatchable = array_create(20); + CHECK_NULL(unwatchable); + if (!unwatchable_mounts(unwatchable)) { + return false; + } + + if (!register_roots(new_roots, unwatchable)) { + return false; + } + + // todo: sort/optimize list + printf("UNWATCHEABLE\n"); + for (int i=0; iname); + unwatch(root->id); + free(root->name); + free(root); + }; +} + + +static bool register_roots(array* new_roots, array* unwatchable) { + for (int i=0; i= 0) { + watch_root* root = malloc(sizeof(watch_root)); + CHECK_NULL(root); + root->id = id; + root->name = new_root; + CHECK_NULL(array_push(roots, root)); + } + else { + if (show_warning && watch_limit_reached()) { + int limit = get_watch_count(); + syslog(LOG_WARNING, "watch limit (%d) reached", limit); + printf("MESSAGE\n" INOTIFY_LIMIT_MSG, limit); + show_warning = false; // warn only once + } + CHECK_NULL(array_push(unwatchable, new_root)); + } + } + + return true; +} + +static bool is_watchable(const char* dev, const char* mnt, const char* fs) { + // don't watch special and network filesystems + return !(strncmp(mnt, "/dev", 4) == 0 || strncmp(mnt, "/proc", 5) == 0 || strncmp(mnt, "/sys", 4) == 0 || + strcmp(fs, "fuse.gvfs-fuse-daemon") == 0 || strcmp(fs, "cifs") == 0 || strcmp(fs, "nfs") == 0); +} + +#define MTAB_DELIMS " \t" + +static bool unwatchable_mounts(array* mounts) { + FILE* mtab = fopen("/etc/mtab", "r"); + if (mtab == NULL) { + mtab = fopen("/proc/mounts", "r"); + } + if (mtab == NULL) { + syslog(LOG_ERR, "neither /etc/mtab nor /proc/mounts can be read"); + return false; + } + + char* line; + while ((line = read_line(mtab)) != NULL) { + syslog(LOG_DEBUG, "mtab: %s", line); + char* dev = strtok(line, MTAB_DELIMS); + char* point = strtok(NULL, MTAB_DELIMS); + char* fs = strtok(NULL, MTAB_DELIMS); + + if (dev == NULL || point == NULL || fs == NULL) { + syslog(LOG_ERR, "can't parse mount line"); + return false; + } + + if (!is_watchable(dev, point, fs)) { + CHECK_NULL(array_push(mounts, strdup(point))); + } + } + + fclose(mtab); + return true; +} + + +static void inotify_callback(char* path, int event) { + if (event & IN_CREATE || event & IN_MOVED_TO) { + printf("CREATE\n%s\n", path); + syslog(LOG_DEBUG, "CREATE: %s", path); + return; + } + + if (event & IN_MODIFY) { + printf("CHANGE\n%s\n", path); + syslog(LOG_DEBUG, "CHANGE: %s", path); + return; + } + + if (event & IN_ATTRIB) { + printf("STATS\n%s\n", path); + syslog(LOG_DEBUG, "STATS: %s", path); + return; + } + + if (event & IN_DELETE || event & IN_MOVED_FROM) { + printf("DELETE\n%s\n", path); + syslog(LOG_DEBUG, "DELETE: %s", path); + return; + } + + if (event & IN_UNMOUNT) { + printf("RESET\n"); + syslog(LOG_DEBUG, "RESET"); + return; + } +} diff --git a/native/fsNotifier/linux/make.sh b/native/fsNotifier/linux/make.sh new file mode 100755 index 000000000000..74eeb734e648 --- /dev/null +++ b/native/fsNotifier/linux/make.sh @@ -0,0 +1 @@ +gcc -O2 -m32 -Wall -std=c99 -D_BSD_SOURCE -D_XOPEN_SOURCE=500 -o fsnotifier main.c inotify.c util.c diff --git a/native/fsNotifier/linux/util.c b/native/fsNotifier/linux/util.c new file mode 100644 index 000000000000..6e09a9053d80 --- /dev/null +++ b/native/fsNotifier/linux/util.c @@ -0,0 +1,190 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fsnotifier.h" + +#include +#include +#include + + +#define REALLOC_FACTOR 2 + +struct __array { + void** data; + int size; + int capacity; +}; + +static bool array_realloc(array* a) { + if (a->size == a->capacity) { + int new_cap = a->capacity * REALLOC_FACTOR; + void* new_ptr = realloc(a->data, sizeof(void*) * new_cap); + if (new_ptr == NULL) { + return false; + } + a->capacity = new_cap; + a->data = new_ptr; + } + return true; +} + +array* array_create(int initial_capacity) { + array* a = (array*) malloc(sizeof(array)); + if (a == NULL) { + return NULL; + } + + a->data = calloc(sizeof(void*), initial_capacity); + if (a->data == NULL) { + free(a); + return NULL; + } + + a->capacity = initial_capacity; + a->size = 0; + + return a; +} + +inline int array_size(array* a) { + return (a != NULL ? a->size : 0); +} + +void* array_push(array* a, void* element) { + if (a == NULL || !array_realloc(a)) { + return NULL; + } + a->data[a->size++] = element; + return element; +} + +void* array_pop(array* a) { + if (a != NULL && a->size > 0) { + return a->data[--a->size]; + } + else { + return NULL; + } +} + +void array_put(array* a, int index, void* element) { + if (a != NULL && index >=0 && index < a->capacity) { + a->data[index] = element; + if (a->size <= index) { + a->size = index + 1; + } + } +} + +void* array_get(array* a, int index) { + if (a != NULL && index >= 0 && index < a->size) { + return a->data[index]; + } + else { + return NULL; + } +} + +void array_delete(array* a) { + if (a != NULL) { + free(a->data); + free(a); + } +} + +void array_delete_vs_data(array* a) { + if (a != NULL) { + for (int i=0; isize; i++) { + if (a->data[i] != NULL) { + free(a->data[i]); + } + } + array_delete(a); + } +} + + +struct __table { + void** data; + int capacity; +}; + +table* table_create(int capacity) { + table* t = malloc(sizeof(table)); + if (t == NULL) { + return NULL; + } + + t->data = calloc(sizeof(void*), capacity); + if (t->data == NULL) { + free(t); + return NULL; + } + memset(t->data, 0, sizeof(void*) * capacity); + + t->capacity = capacity; + + return t; +} + +static inline int wrap(int key, table* t) { + return (t != NULL ? key % t->capacity : -1); +} + +// todo: resolve collisions (?) +void* table_put(table* t, int key, void* value) { + int k = wrap(key, t); + if (k < 0 || (value != NULL && t->data[k] != NULL)) { + return NULL; + } + else { + return t->data[k] = value; + } +} + +void* table_get(table* t, int key) { + int k = wrap(key, t); + if (k < 0) { + return NULL; + } + else { + return t->data[k]; + } +} + +void table_delete(table* t) { + if (t != NULL) { + free(t->data); + free(t); + } +} + + +#define INPUT_BUF_LEN 2048 +static char input_buf[INPUT_BUF_LEN]; + +char* read_line(FILE* stream) { + char* retval = fgets(input_buf, INPUT_BUF_LEN, stream); + if (retval == NULL || feof(stream)) { + return NULL; + } + int pos = strlen(input_buf) - 1; + if (input_buf[pos] == '\n') { + input_buf[pos] = '\0'; + } + return input_buf; +} From aa00732d8f9905c8bc3ee4d1b64aec24146e1c0f Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Thu, 11 Mar 2010 13:51:27 +0300 Subject: [PATCH 02/71] handel unboxing of null values --- .../engine/evaluation/expression/UnBoxingEvaluator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/UnBoxingEvaluator.java b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/UnBoxingEvaluator.java index faa963756f8d..efbc8d3def9c 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/UnBoxingEvaluator.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/UnBoxingEvaluator.java @@ -58,6 +58,9 @@ public class UnBoxingEvaluator implements Evaluator{ public Object evaluate(EvaluationContextImpl context) throws EvaluateException { final Value result = (Value)myOperand.evaluate(context); + if (result == null) { + throw new EvaluateException("java.lang.NullPointerException: cannot unbox null value"); + } if (result instanceof ObjectReference) { final String valueTypeName = result.type().name(); final Pair pair = TYPES_TO_CONVERSION_METHOD_MAP.get(valueTypeName); From 57f9a3686249fdff0fa5617e052e8033a51060c6 Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Thu, 11 Mar 2010 14:39:24 +0300 Subject: [PATCH 03/71] fix NPE --- .../src/com/intellij/debugger/ui/impl/tree/TreeBuilder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/tree/TreeBuilder.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/tree/TreeBuilder.java index cb8a02936816..96866038a619 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/tree/TreeBuilder.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/tree/TreeBuilder.java @@ -122,7 +122,10 @@ public abstract class TreeBuilder implements TreeModel { } public void removeNodeFromParent(TreeBuilderNode node) { - ((TreeBuilderNode) node.getParent()).remove(node); + final TreeBuilderNode parent = (TreeBuilderNode)node.getParent(); + if (parent != null) { + parent.remove(node); + } } } From 4242c3e4420246224cb60ce7b3d818d1917325f8 Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Thu, 11 Mar 2010 14:57:37 +0300 Subject: [PATCH 04/71] filter zombie threads when building a thread dump --- .../src/com/intellij/debugger/actions/ThreadDumpAction.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/ThreadDumpAction.java b/java/debugger/impl/src/com/intellij/debugger/actions/ThreadDumpAction.java index 2642f221e4a5..bb096870362c 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/ThreadDumpAction.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/ThreadDumpAction.java @@ -87,8 +87,11 @@ public class ThreadDumpAction extends AnAction { for (ThreadReference threadReference : threads) { final StringBuilder buffer = new StringBuilder(); boolean hasEmptyStack = true; - final String threadName = threadName(threadReference); final int threadStatus = threadReference.status(); + if (threadStatus == ThreadReference.THREAD_STATUS_ZOMBIE) { + continue; + } + final String threadName = threadName(threadReference); final ThreadState threadState = new ThreadState(threadName, threadStatusToState(threadStatus)); nameToThreadMap.put(threadName, threadState); result.add(threadState); From 74b6cd609d077febd29e6cc472cea806b281bf66 Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Wed, 10 Mar 2010 22:19:43 +0300 Subject: [PATCH 05/71] fix memory leak on reachable find dialog --- .../lang-impl/src/com/intellij/find/impl/FindDialog.java | 2 +- .../src/com/intellij/find/impl/FindManagerImpl.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java b/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java index 650f949d0e51..40d6b71065cb 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java @@ -68,7 +68,7 @@ import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -final class FindDialog extends DialogWrapper { +class FindDialog extends DialogWrapper { private static final Logger LOG = Logger.getInstance("#com.intellij.find.impl.FindDialog"); private ComboBox myInputComboBox; diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java b/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java index 300c0e4890e2..0f924d4274a4 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java @@ -188,7 +188,13 @@ public class FindManagerImpl extends FindManager implements PersistentStateCompo } okHandler.run(); } - }); + }) { + @Override + protected void dispose() { + super.dispose(); + myFindDialog = null; // avoid strong ref! + } + }; myFindDialog.setModal(false); } myFindDialog.show(); From 6248d2e2b0988bb9336b1fc4151b93ae3d6b3084 Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Thu, 11 Mar 2010 15:44:41 +0300 Subject: [PATCH 06/71] tolerate non local inspection tools returned from InspectionToolPRovider when configuring test inspections from it --- .../fixtures/impl/CodeInsightTestFixtureImpl.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index 12a52ff7399c..8326f588c41d 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -254,11 +254,14 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig for (InspectionToolProvider provider: providers) { for (Class clazz: provider.getInspectionClasses()) { try { - LocalInspectionTool inspection = (LocalInspectionTool)clazz.getConstructor().newInstance(); - tools.add(inspection); + Object o = clazz.getConstructor().newInstance(); + if (o instanceof LocalInspectionTool) { + LocalInspectionTool inspection = (LocalInspectionTool)o; + tools.add(inspection); + } } catch (Exception e) { - throw new RuntimeException("Cannot instantiate " + clazz); + throw new RuntimeException("Cannot instantiate " + clazz, e); } } } From 549170b049116b6912adc0b4db192b48daf4ba13 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Thu, 11 Mar 2010 16:05:17 +0300 Subject: [PATCH 07/71] IDEA-52463 Attempt to show changes on UML diagram from file history causes exception --- .../src/org/jetbrains/idea/svn/SvnVcs.java | 2 +- .../actions/ShowAllSubmittedFilesAction.java | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java index 4599c490880b..4361e36bd50b 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java @@ -981,6 +981,6 @@ public class SvnVcs extends AbstractVcs { @Override public CommittedChangeList getRevisionChanges(VcsFileRevision revision, VirtualFile file) throws VcsException { - return ShowAllSubmittedFilesAction.loadRevisions(getProject(), (SvnFileRevision)revision, file); + return ShowAllSubmittedFilesAction.loadRevisions(getProject(), (SvnFileRevision)revision, file, false); } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowAllSubmittedFilesAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowAllSubmittedFilesAction.java index 5a0968851c77..c82d212bffff 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowAllSubmittedFilesAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowAllSubmittedFilesAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2010 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,6 +92,11 @@ public class ShowAllSubmittedFilesAction extends AnAction implements DumbAware { @Nullable public static SvnChangeList loadRevisions(final Project project, final SvnFileRevision svnRevision, @Nullable final VirtualFile file) { + return loadRevisions(project, svnRevision, file, true); + } + + @Nullable + public static SvnChangeList loadRevisions(final Project project, final SvnFileRevision svnRevision, @Nullable final VirtualFile file, boolean underProgress) { final Ref result = new Ref(); final SvnRevisionNumber number = ((SvnRevisionNumber)svnRevision.getRevisionNumber()); @@ -123,7 +128,7 @@ public class ShowAllSubmittedFilesAction extends AnAction implements DumbAware { } } - ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + final Runnable process = new Runnable() { public void run() { try { @@ -138,13 +143,18 @@ public class ShowAllSubmittedFilesAction extends AnAction implements DumbAware { } ProgressManager.getInstance().getProgressIndicator().setText(SvnBundle.message("progress.text.processing.changes")); - result.set(new SvnChangeList(vcs, location, logEntry [0], repositoryUrl.toString())); + result.set(new SvnChangeList(vcs, location, logEntry[0], repositoryUrl.toString())); } catch (Exception e) { ex[0] = e; } } - }, getTitle(targetRevision.getNumber()), false, project); + }; + if (underProgress) { + ProgressManager.getInstance().runProcessWithProgressSynchronously(process, getTitle(targetRevision.getNumber()), false, project); + } else { + process.run(); + } if (ex[0] != null) throw ex[0]; } catch (Exception e1) { From b425c54e969d9d5dfab0b8183ab3279bb9cf88aa Mon Sep 17 00:00:00 2001 From: Anton Makeev Date: Thu, 11 Mar 2010 17:28:59 +0300 Subject: [PATCH 08/71] Console: correctly navigate to sources using the corresponding scopes --- .../DefaultConsoleFiltersProvider.java | 7 ++++- .../filters/ConsoleFilterProviderEx.java | 29 +++++++++++++++++++ .../filters/TextConsoleBuilderFactory.java | 3 ++ .../TextConsoleBuilderFactoryImpl.java | 5 ++++ .../filters/TextConsoleBuilderImpl.java | 9 +++++- .../execution/impl/ConsoleViewImpl.java | 13 +++++++-- .../testframework/TestConsoleProperties.java | 20 +++++++++++-- .../ui/BaseTestsOutputConsoleView.java | 2 +- .../ui/properties/JUnitConsoleProperties.java | 5 ++++ .../testng/model/TestNGConsoleProperties.java | 6 ++++ 10 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 platform/lang-api/src/com/intellij/execution/filters/ConsoleFilterProviderEx.java diff --git a/java/execution/impl/src/com/intellij/execution/filters/DefaultConsoleFiltersProvider.java b/java/execution/impl/src/com/intellij/execution/filters/DefaultConsoleFiltersProvider.java index d96ce227762c..1f85d105a237 100644 --- a/java/execution/impl/src/com/intellij/execution/filters/DefaultConsoleFiltersProvider.java +++ b/java/execution/impl/src/com/intellij/execution/filters/DefaultConsoleFiltersProvider.java @@ -21,10 +21,15 @@ package com.intellij.execution.filters; import com.intellij.openapi.project.Project; +import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; -public class DefaultConsoleFiltersProvider implements ConsoleFilterProvider{ +public class DefaultConsoleFiltersProvider implements ConsoleFilterProviderEx { public Filter[] getDefaultFilters(@NotNull Project project) { return new Filter[]{new ExceptionFilter(project), new YourkitFilter(project)}; } + + public Filter[] getDefaultFilters(@NotNull Project project, @NotNull GlobalSearchScope scope) { + return new Filter[]{new ExceptionFilter(scope), new YourkitFilter(project)}; + } } \ No newline at end of file diff --git a/platform/lang-api/src/com/intellij/execution/filters/ConsoleFilterProviderEx.java b/platform/lang-api/src/com/intellij/execution/filters/ConsoleFilterProviderEx.java new file mode 100644 index 000000000000..ab4bcb5bf1ad --- /dev/null +++ b/platform/lang-api/src/com/intellij/execution/filters/ConsoleFilterProviderEx.java @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * User: anna + * Date: 20-Aug-2007 + */ +package com.intellij.execution.filters; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.search.GlobalSearchScope; +import org.jetbrains.annotations.NotNull; + +public interface ConsoleFilterProviderEx extends ConsoleFilterProvider { + Filter[] getDefaultFilters(@NotNull Project project, @NotNull GlobalSearchScope scope); +} \ No newline at end of file diff --git a/platform/lang-api/src/com/intellij/execution/filters/TextConsoleBuilderFactory.java b/platform/lang-api/src/com/intellij/execution/filters/TextConsoleBuilderFactory.java index 11589504725e..569df7ce6353 100644 --- a/platform/lang-api/src/com/intellij/execution/filters/TextConsoleBuilderFactory.java +++ b/platform/lang-api/src/com/intellij/execution/filters/TextConsoleBuilderFactory.java @@ -24,6 +24,7 @@ package com.intellij.execution.filters; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; +import com.intellij.psi.search.GlobalSearchScope; /** * @author dyoma @@ -31,6 +32,8 @@ import com.intellij.openapi.project.Project; public abstract class TextConsoleBuilderFactory { public abstract TextConsoleBuilder createBuilder(Project project); + public abstract TextConsoleBuilder createBuilder(Project project, GlobalSearchScope scope); + public static TextConsoleBuilderFactory getInstance() { return ServiceManager.getService(TextConsoleBuilderFactory.class); } diff --git a/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderFactoryImpl.java b/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderFactoryImpl.java index d649c60f1457..2e5d71a387e7 100644 --- a/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderFactoryImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderFactoryImpl.java @@ -23,6 +23,7 @@ package com.intellij.execution.filters; import com.intellij.openapi.project.Project; +import com.intellij.psi.search.GlobalSearchScope; /** * @author dyoma @@ -32,4 +33,8 @@ public class TextConsoleBuilderFactoryImpl extends TextConsoleBuilderFactory { return new TextConsoleBuilderImpl(project); } + @Override + public TextConsoleBuilder createBuilder(Project project, GlobalSearchScope scope) { + return new TextConsoleBuilderImpl(project, scope); + } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderImpl.java b/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderImpl.java index f90743efe786..35719ed12726 100644 --- a/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderImpl.java @@ -19,6 +19,7 @@ package com.intellij.execution.filters; import com.intellij.execution.impl.ConsoleViewImpl; import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.project.Project; +import com.intellij.psi.search.GlobalSearchScope; import java.util.ArrayList; @@ -27,11 +28,17 @@ import java.util.ArrayList; */ public class TextConsoleBuilderImpl extends TextConsoleBuilder { private final Project myProject; + private final GlobalSearchScope myScope; private final ArrayList myFilters = new ArrayList(); private boolean myViewer; public TextConsoleBuilderImpl(final Project project) { + this(project, GlobalSearchScope.allScope(project)); + } + + public TextConsoleBuilderImpl(final Project project, GlobalSearchScope scope) { myProject = project; + myScope = scope; } public ConsoleView getConsole() { @@ -43,7 +50,7 @@ public class TextConsoleBuilderImpl extends TextConsoleBuilder { } protected ConsoleViewImpl createConsole() { - return new ConsoleViewImpl(myProject, myViewer); + return new ConsoleViewImpl(myProject, myScope, myViewer, null); } public void addFilter(final Filter filter) { diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java index d5e20a06b5dd..ee8a304c5bfd 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java @@ -68,6 +68,7 @@ import com.intellij.pom.Navigatable; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.tree.IElementType; import com.intellij.util.Alarm; import com.intellij.util.EditorPopupHandler; @@ -237,6 +238,11 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo } public ConsoleViewImpl(final Project project, boolean viewer, FileType fileType) { + this(project, GlobalSearchScope.allScope(project), viewer, fileType); + } + + + public ConsoleViewImpl(final Project project, GlobalSearchScope searchScope, boolean viewer, FileType fileType) { super(new BorderLayout()); isViewer = viewer; myPsiDisposedCheck = new DisposedPsiManagerCheck(project); @@ -245,8 +251,11 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo myCustomFilter = new CompositeFilter(project); myPredefinedMessageFilter = new CompositeFilter(project); - for (ConsoleFilterProvider filterProvider : Extensions.getExtensions(ConsoleFilterProvider.FILTER_PROVIDERS)) { - for (Filter filter : filterProvider.getDefaultFilters(project)) { + for (ConsoleFilterProvider eachProvider : Extensions.getExtensions(ConsoleFilterProvider.FILTER_PROVIDERS)) { + Filter[] filters = eachProvider instanceof ConsoleFilterProviderEx + ? ((ConsoleFilterProviderEx)eachProvider).getDefaultFilters(project, searchScope) + : eachProvider.getDefaultFilters(project); + for (Filter filter : filters) { myPredefinedMessageFilter.addFilter(filter); } } diff --git a/platform/testRunner/src/com/intellij/execution/testframework/TestConsoleProperties.java b/platform/testRunner/src/com/intellij/execution/testframework/TestConsoleProperties.java index 3393e0c4a6b2..74d432d106a7 100644 --- a/platform/testRunner/src/com/intellij/execution/testframework/TestConsoleProperties.java +++ b/platform/testRunner/src/com/intellij/execution/testframework/TestConsoleProperties.java @@ -25,7 +25,9 @@ import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ExecutionConsole; import com.intellij.execution.util.StoringPropertyContainer; import com.intellij.openapi.Disposable; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.config.AbstractProperty; import com.intellij.util.config.BooleanProperty; import com.intellij.util.config.Storage; @@ -46,14 +48,28 @@ public abstract class TestConsoleProperties extends StoringPropertyContainer imp private final Project myProject; private ConsoleView myConsole; - protected final HashMap> myListeners = new HashMap>(); + protected final HashMap> myListeners = + new HashMap>(); public TestConsoleProperties(final Storage storage, Project project) { super(storage); myProject = project; } - public Project getProject() { return myProject; } + public Project getProject() { + return myProject; + } + + public GlobalSearchScope getScope() { + Module[] modules = getConfiguration().getModules(); + if (modules.length == 0) return GlobalSearchScope.allScope(myProject); + + GlobalSearchScope scope = GlobalSearchScope.EMPTY_SCOPE; + for (Module each : modules) { + scope = scope.uniteWith(GlobalSearchScope.moduleRuntimeScope(each, true)); + } + return scope; + } public void addListener(final AbstractProperty property, final TestFrameworkPropertyListener listener) { ArrayList listeners = myListeners.get(property); diff --git a/platform/testRunner/src/com/intellij/execution/testframework/ui/BaseTestsOutputConsoleView.java b/platform/testRunner/src/com/intellij/execution/testframework/ui/BaseTestsOutputConsoleView.java index 6d473e2d8493..6e1880baecc8 100644 --- a/platform/testRunner/src/com/intellij/execution/testframework/ui/BaseTestsOutputConsoleView.java +++ b/platform/testRunner/src/com/intellij/execution/testframework/ui/BaseTestsOutputConsoleView.java @@ -40,7 +40,7 @@ public abstract class BaseTestsOutputConsoleView implements ConsoleView, Observa public BaseTestsOutputConsoleView(final TestConsoleProperties properties) { myProperties = properties; - myConsole = TextConsoleBuilderFactory.getInstance().createBuilder(properties.getProject()).getConsole(); + myConsole = TextConsoleBuilderFactory.getInstance().createBuilder(properties.getProject(), myProperties.getScope()).getConsole(); myPrinter = new TestsOutputConsolePrinter(myConsole, properties); myProperties.setConsole(this); diff --git a/plugins/junit/src/com/intellij/execution/junit2/ui/properties/JUnitConsoleProperties.java b/plugins/junit/src/com/intellij/execution/junit2/ui/properties/JUnitConsoleProperties.java index 495fd82e4d25..c5793c32088d 100644 --- a/plugins/junit/src/com/intellij/execution/junit2/ui/properties/JUnitConsoleProperties.java +++ b/plugins/junit/src/com/intellij/execution/junit2/ui/properties/JUnitConsoleProperties.java @@ -19,6 +19,7 @@ package com.intellij.execution.junit2.ui.properties; import com.intellij.execution.junit.JUnitConfiguration; import com.intellij.execution.testframework.JavaAwareTestConsoleProperties; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.config.Storage; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -39,4 +40,8 @@ public class JUnitConsoleProperties extends JavaAwareTestConsoleProperties { public JUnitConfiguration getConfiguration() { return myConfiguration; } + @Override + public GlobalSearchScope getScope() { + return myConfiguration.getPersistentData().getScope().getSourceScope(myConfiguration).getLibrariesScope(); + } } diff --git a/plugins/testng/src/com/theoryinpractice/testng/model/TestNGConsoleProperties.java b/plugins/testng/src/com/theoryinpractice/testng/model/TestNGConsoleProperties.java index 517c2590fb45..ed87ce72230c 100644 --- a/plugins/testng/src/com/theoryinpractice/testng/model/TestNGConsoleProperties.java +++ b/plugins/testng/src/com/theoryinpractice/testng/model/TestNGConsoleProperties.java @@ -17,6 +17,7 @@ package com.theoryinpractice.testng.model; import com.intellij.execution.testframework.JavaAwareTestConsoleProperties; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.config.Storage; import com.theoryinpractice.testng.configuration.TestNGConfiguration; import org.jetbrains.annotations.NonNls; @@ -35,4 +36,9 @@ public class TestNGConsoleProperties extends JavaAwareTestConsoleProperties { { return myConfiguration; } + + @Override + public GlobalSearchScope getScope() { + return myConfiguration.getPersistantData().getScope().getSourceScope(myConfiguration).getLibrariesScope(); + } } From ab974561a28346d8d62c9eb5b84bcd707d86f396 Mon Sep 17 00:00:00 2001 From: Maxim Shafirov Date: Wed, 3 Mar 2010 21:05:36 +0300 Subject: [PATCH 09/71] Move indents logic out of editor implementation into separate indents model --- .../intention/impl/config/LazyEditor.java | 4 +- .../injected/editor/EditorWindow.java | 4 +- .../actions/SelectWordAtCaretAction.java | 2 +- .../com/intellij/openapi/editor/Editor.java | 3 +- .../intellij/openapi/editor/IndentsModel.java | 32 +++++ .../openapi/editor/impl/EditorImpl.java | 93 ++----------- .../editor/impl/EmptyIndentsModel.java | 40 ++++++ .../openapi/editor/impl/IndentsModelImpl.java | 124 ++++++++++++++++++ .../editor/textarea/TextComponentEditor.java | 5 +- 9 files changed, 213 insertions(+), 94 deletions(-) create mode 100644 platform/platform-api/src/com/intellij/openapi/editor/IndentsModel.java create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyIndentsModel.java create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/LazyEditor.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/LazyEditor.java index cf3925ee4d0c..5102e1aceb86 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/LazyEditor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/LazyEditor.java @@ -205,7 +205,7 @@ class LazyEditor extends UserDataHolderBase implements Editor { return getEditor().getHeaderComponent(); } - public IndentGuideDescriptor getCaretIndentGuide() { - return getEditor().getCaretIndentGuide(); + public IndentsModel getIndentsModel() { + return getEditor().getIndentsModel(); } } diff --git a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java index c5be4b493b33..180a8eb09d55 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java @@ -597,7 +597,7 @@ public class EditorWindow implements EditorEx, UserDataHolderEx { return myDelegate.calcColumnNumber(myDelegate.getDocument().getText(), hostStart, hostOffset, tabSize); } - public IndentGuideDescriptor getCaretIndentGuide() { - return null; // Caret guide is purely text-based thing so it is handled at top editor level. + public IndentsModel getIndentsModel() { + return myDelegate.getIndentsModel(); } } diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java index f4157b585723..365b5e34e291 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java @@ -91,7 +91,7 @@ public class SelectWordAtCaretAction extends TextComponentEditorAction implement @Override public void execute(Editor editor, DataContext dataContext) { - final IndentGuideDescriptor guide = editor.getCaretIndentGuide(); + final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide(); final SelectionModel selectionModel = editor.getSelectionModel(); if (guide != null && !selectionModel.hasSelection() && !selectionModel.hasBlockSelection() && isWhitespaceAtCaret(editor)) { selectWithGuide(editor, guide); diff --git a/platform/platform-api/src/com/intellij/openapi/editor/Editor.java b/platform/platform-api/src/com/intellij/openapi/editor/Editor.java index b9f1ff229286..510f587821ce 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/Editor.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/Editor.java @@ -309,6 +309,5 @@ public interface Editor extends UserDataHolder { @Nullable JComponent getHeaderComponent(); - @Nullable - IndentGuideDescriptor getCaretIndentGuide(); + IndentsModel getIndentsModel(); } diff --git a/platform/platform-api/src/com/intellij/openapi/editor/IndentsModel.java b/platform/platform-api/src/com/intellij/openapi/editor/IndentsModel.java new file mode 100644 index 000000000000..833af40042f6 --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/editor/IndentsModel.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author max + */ +package com.intellij.openapi.editor; + +import org.jetbrains.annotations.Nullable; + +public interface IndentsModel { + int getIndentLevel(int line); + int getIndentSize(); + + void assumeIndent(int line, int level); + + @Nullable + IndentGuideDescriptor getCaretIndentGuide(); +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 3a8cdaeda71a..4798d8686692 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -20,7 +20,6 @@ import com.intellij.codeInsight.hint.DocumentFragmentTooltipRenderer; import com.intellij.codeInsight.hint.EditorFragmentComponent; import com.intellij.codeInsight.hint.TooltipController; import com.intellij.codeInsight.hint.TooltipGroup; -import com.intellij.codeStyle.CodeStyleFacade; import com.intellij.concurrency.JobScheduler; import com.intellij.ide.*; import com.intellij.ide.dnd.DnDManager; @@ -65,7 +64,6 @@ import com.intellij.util.Alarm; import com.intellij.util.IJSwingUtilities; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; -import com.intellij.util.text.CharArrayUtil; import com.intellij.util.ui.EmptyClipboardOwner; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.update.UiNotifyConnector; @@ -226,7 +224,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private char[] myPrefixText; private TextAttributes myPrefixAttributes; private IndentGuideDescriptor myCaretIndentGuide = null; - private int myIndentSize = -1; + private IndentsModel myIndentsModel; static { ourCaretBlinkingCommand = new RepaintCursorCommand(); @@ -285,12 +283,13 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myDocument.addDocumentListener(mySelectionModel); myDocument.addDocumentListener(myEditorDocumentAdapter); + myIndentsModel = new IndentsModelImpl(this); myCaretModel.addCaretListener(new CaretListener() { LightweightHint myCurrentHint = null; public void caretPositionChanged(CaretEvent e) { - final IndentGuideDescriptor newGuide = getCaretIndentGuide(); + final IndentGuideDescriptor newGuide = myIndentsModel.getCaretIndentGuide(); if (!Comparing.equal(newGuide, myCaretIndentGuide)) { repaintGuide(newGuide); repaintGuide(myCaretIndentGuide); @@ -443,7 +442,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myCharHeight = -1; myLineHeight = -1; myDescent = -1; - myIndentSize = -1; myPlainFontMetrics = null; myCaretModel.reinitSettings(); @@ -1231,13 +1229,13 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int y = clip.y; int line = xyToLogicalPosition(new Point(0, y)).line; - final int indentSize = getIndentSize(); + final int indentSize = myIndentsModel.getIndentSize(); int gapWidth = EditorUtil.getSpaceWidth(Font.PLAIN, this) * indentSize; final CharSequence chars = myDocument.getCharsNoThreadCheck(); int prevIndent = -1; do { - final int indents = getIndents(line); + final int indents = myIndentsModel.getIndentLevel(line); /* if (prevIndent > indents && isWhitespaceAt(chars, x, y)) { @@ -1298,39 +1296,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } - @Nullable - public IndentGuideDescriptor getCaretIndentGuide() { - if (!mySettings.isIndentGuidesShown()) return null; - - final int indentSize = getIndentSize(); - if (indentSize == 0) return null; - - final LogicalPosition caretPosition = myCaretModel.getLogicalPosition(); - int startLine = caretPosition.line; - int endLine = startLine; - final int caretIndent = caretPosition.column / indentSize; - final int indents = getIndents(startLine); - - if (caretIndent * indentSize != caretPosition.column) return null; - if (caretIndent > indents|| indents == 0) return null; - - if (caretIndent > 0 && caretPosition.column % indentSize == 0) { - while (startLine > 0) { - if (getIndents(startLine - 1) <= caretIndent) break; - startLine--; - } - - while (endLine < myDocument.getLineCount() - 1) { - if (getIndents(endLine + 1) <= caretIndent) break; - endLine++; - } - - if (startLine < endLine) { - return new IndentGuideDescriptor(caretIndent, startLine, endLine, indentSize); - } - } - - return null; + public IndentsModel getIndentsModel() { + return myIndentsModel; } public void setHeaderComponent(JComponent header) { @@ -2284,50 +2251,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return line; } - private int getIndentSize() { - if (myIndentSize == -1) { - if (myProject == null || myProject.isDisposed() || myVirtualFile == null) return EditorUtil.getTabSize(this); - myIndentSize = CodeStyleFacade.getInstance(myProject).getIndentSize(myVirtualFile.getFileType()); - } - return myIndentSize; - } - - private int getIndents(int line, boolean goUp, boolean goDown) { - if (line <= 0 || line >= myDocument.getLineCount()) return 0; - int lineStart = myDocument.getLineStartOffset(line); - int lineEnd = myDocument.getLineEndOffset(line); - - CharSequence chars = myDocument.getCharsNoThreadCheck(); - int nonWhitespaceOffset = CharArrayUtil.shiftForward(chars, lineStart, " \t"); - if (nonWhitespaceOffset < lineEnd) { - final int columnNumber = calcColumnNumber(nonWhitespaceOffset, line); - final int indentSize = getIndentSize(); - return indentSize != 0 ? columnNumber / indentSize : columnNumber; - } - else { - int upIndent = goUp ? getIndents(line - 1, true, false) : 100; - int downIndent = goDown ? getIndents(line + 1, false, true) : 100; - return Math.min(upIndent, downIndent); - } - } - - private int getIndents(int line) { - int answer = getIndents(line, true, true); - if (answer == 0) return 0; - - int prev; - do { - prev = getIndents(--line, true, false); - } - while (line > 0 && prev == answer); - - if (answer - 2 > prev) { - return prev + 1; - } - - return answer; - } - @NotNull public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos) { assertReadAccess(); @@ -2482,7 +2405,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return lineIndex; } - private int calcColumnNumber(int offset, int lineIndex) { + int calcColumnNumber(int offset, int lineIndex) { if (myDocument.getTextLength() == 0) return 0; CharSequence text = myDocument.getCharsSequence(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyIndentsModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyIndentsModel.java new file mode 100644 index 000000000000..7148ffa9641c --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyIndentsModel.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author max + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.editor.IndentGuideDescriptor; +import com.intellij.openapi.editor.IndentsModel; + +public class EmptyIndentsModel implements IndentsModel { + public int getIndentLevel(int line) { + return 0; + } + + public int getIndentSize() { + return 4; + } + + public void assumeIndent(int line, int level) { + } + + public IndentGuideDescriptor getCaretIndentGuide() { + return null; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java new file mode 100644 index 000000000000..57e235096282 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java @@ -0,0 +1,124 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author max + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.codeStyle.CodeStyleFacade; +import com.intellij.openapi.editor.EditorSettings; +import com.intellij.openapi.editor.IndentGuideDescriptor; +import com.intellij.openapi.editor.IndentsModel; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.editor.ex.util.EditorUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.text.CharArrayUtil; +import gnu.trove.TIntArrayList; + +public class IndentsModelImpl implements IndentsModel { + private EditorImpl myEditor; + private int myIndentSize = -1; + + public IndentsModelImpl(EditorImpl editor) { + myEditor = editor; + } + + public int getIndentLevel(int line) { + int answer = getIndents(line, true, true); + if (answer == 0) return 0; + + int prev; + do { + prev = getIndents(--line, true, false); + } + while (line > 0 && prev == answer); + + if (answer - 2 > prev) { + return prev + 1; + } + + return answer; + } + + private int getIndents(int line, boolean goUp, boolean goDown) { + DocumentImpl myDocument = (DocumentImpl)myEditor.getDocument(); + if (line <= 0 || line >= myDocument.getLineCount()) return 0; + int lineStart = myDocument.getLineStartOffset(line); + int lineEnd = myDocument.getLineEndOffset(line); + + CharSequence chars = myDocument.getCharsNoThreadCheck(); + int nonWhitespaceOffset = CharArrayUtil.shiftForward(chars, lineStart, " \t"); + if (nonWhitespaceOffset < lineEnd) { + return myEditor.calcColumnNumber(nonWhitespaceOffset, line) / getIndentSize(); + } + else { + int upIndent = goUp ? getIndents(line - 1, true, false) : 100; + int downIndent = goDown ? getIndents(line + 1, false, true) : 100; + return Math.min(upIndent, downIndent); + } + } + + public int getIndentSize() { + if (myIndentSize == -1) { + Project project = myEditor.getProject(); + VirtualFile vFile = myEditor.getVirtualFile(); + if (project == null || project.isDisposed() || vFile == null) return EditorUtil.getTabSize(myEditor); + myIndentSize = CodeStyleFacade.getInstance(project).getIndentSize(vFile.getFileType()); + } + return myIndentSize; + } + + public void assumeIndent(int line, int level) { + throw new UnsupportedOperationException("assumeIndent is not implemented"); // TODO + } + + public IndentGuideDescriptor getCaretIndentGuide() { + EditorSettings settings = myEditor.getSettings(); + if (!settings.isIndentGuidesShown()) return null; + + final int indentSize = getIndentSize(); + if (indentSize == 0) return null; + + final LogicalPosition caretPosition = myEditor.getCaretModel().getLogicalPosition(); + int startLine = caretPosition.line; + int endLine = startLine; + final int caretIndent = caretPosition.column / indentSize; + final int indents = getIndentLevel(startLine); + + if (caretIndent * indentSize != caretPosition.column) return null; + if (caretIndent > indents|| indents == 0) return null; + + if (caretIndent > 0 && caretPosition.column % indentSize == 0) { + while (startLine > 0) { + if (getIndentLevel(startLine - 1) <= caretIndent) break; + startLine--; + } + + while (endLine < myEditor.getDocument().getLineCount() - 1) { + if (getIndentLevel(endLine + 1) <= caretIndent) break; + endLine++; + } + + if (startLine < endLine) { + return new IndentGuideDescriptor(caretIndent, startLine, endLine, indentSize); + } + } + + return null; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentEditor.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentEditor.java index 9ecc0e61bb9f..30b25e655a12 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentEditor.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentEditor.java @@ -20,6 +20,7 @@ import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.event.EditorMouseEventArea; import com.intellij.openapi.editor.event.EditorMouseListener; import com.intellij.openapi.editor.event.EditorMouseMotionListener; +import com.intellij.openapi.editor.impl.EmptyIndentsModel; import com.intellij.openapi.editor.impl.SettingsImpl; import com.intellij.openapi.editor.markup.MarkupModel; import com.intellij.openapi.project.Project; @@ -227,7 +228,7 @@ public class TextComponentEditor extends UserDataHolderBase implements Editor { return null; } - public IndentGuideDescriptor getCaretIndentGuide() { - return null; + public IndentsModel getIndentsModel() { + return new EmptyIndentsModel(); } } From bd8c90d2561bdd358a48b22acde85dde3f139ca6 Mon Sep 17 00:00:00 2001 From: Maxim Shafirov Date: Thu, 11 Mar 2010 18:29:39 +0300 Subject: [PATCH 10/71] Indent guides re-implemented --- .../codeInsight/daemon/impl/IndentsPass.java | 278 ++++++++++++++++++ .../daemon/impl/IndentsPassFactory.java | 49 +++ .../injected/editor/EditorWindow.java | 4 + .../actions/SelectWordAtCaretAction.java | 9 +- .../openapi/editor/IndentGuideDescriptor.java | 4 +- .../intellij/openapi/editor/IndentsModel.java | 9 +- .../markup/CustomHighlighterRenderer.java | 28 ++ .../editor/markup/RangeHighlighter.java | 7 +- .../intellij/openapi/editor/ex/EditorEx.java | 2 + .../openapi/editor/impl/EditorImpl.java | 99 ++----- .../editor/impl/EmptyIndentsModel.java | 15 +- .../openapi/editor/impl/IndentsModelImpl.java | 105 +------ .../editor/impl/RangeHighlighterImpl.java | 9 + .../src/componentSets/Lang.xml | 4 + .../intellij/util/containers/IntStack.java | 77 +++++ 15 files changed, 506 insertions(+), 193 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPassFactory.java create mode 100644 platform/platform-api/src/com/intellij/openapi/editor/markup/CustomHighlighterRenderer.java create mode 100644 platform/util/src/com/intellij/util/containers/IntStack.java diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java new file mode 100644 index 000000000000..681edde54672 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java @@ -0,0 +1,278 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author max + */ +package com.intellij.codeInsight.daemon.impl; + +import com.intellij.codeHighlighting.TextEditorHighlightingPass; +import com.intellij.codeInsight.highlighting.BraceMatchingUtil; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.editor.markup.CustomHighlighterRenderer; +import com.intellij.openapi.editor.markup.HighlighterTargetArea; +import com.intellij.openapi.editor.markup.MarkupModel; +import com.intellij.openapi.editor.markup.RangeHighlighter; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiFile; +import com.intellij.util.containers.IntStack; +import com.intellij.util.text.CharArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class IndentsPass extends TextEditorHighlightingPass implements DumbAware { + private static final Key> INDENT_HIGHLIGHTERS_IN_EDITOR_KEY = Key.create("INDENT_HIGHLIGHTERS_IN_EDITOR_KEY"); + private static final Key LAST_TIME_INDENTS_BUILT = Key.create("LAST_TIME_INDENTS_BUILT"); + + private final EditorEx myEditor; + private final PsiFile myFile; + public static final Comparator RANGE_COMPARATOR = new Comparator() { + public int compare(TextRange o1, TextRange o2) { + if (o1.getStartOffset() == o2.getStartOffset()) { + return o1.getEndOffset() - o2.getEndOffset(); + } + + return o1.getStartOffset() - o2.getStartOffset(); + } + }; + + private static final CustomHighlighterRenderer RENDERER = new CustomHighlighterRenderer() { + public void paint(Editor editor, + RangeHighlighter highlighter, + Graphics g) { + int startOffset = highlighter.getStartOffset(); + final Document doc = highlighter.getDocument(); + if (startOffset >= doc.getTextLength()) return; + + int off; + int startLine = doc.getLineNumber(startOffset); + do { + int pos = doc.getLineStartOffset(startLine); + off = CharArrayUtil.shiftForward(doc.getCharsSequence(), pos, " \t"); + startLine--; + } + while (startLine > 1 && doc.getCharsSequence().charAt(off) == '\n'); + + final VisualPosition startPosition = editor.offsetToVisualPosition(off); + if (startPosition.column <= 0) return; + + if (editor.getFoldingModel().isOffsetCollapsed(off)) return; + + final int endOffset = highlighter.getEndOffset(); + final boolean selected; + final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide(); + if (guide != null) { + final CaretModel caretModel = editor.getCaretModel(); + final int caretOffset = caretModel.getOffset(); + selected = + caretOffset >= off && caretOffset < endOffset && caretModel.getLogicalPosition().column == startPosition.column; + } + else { + selected = false; + } + + Point start = editor.visualPositionToXY(new VisualPosition(startPosition.line + 1, startPosition.column)); + final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset); + Point end = editor.visualPositionToXY(new VisualPosition(endPosition.line, endPosition.column)); + g.setColor(selected ? new Color(200, 200, 200) : new Color(230, 230, 230)); + g.drawLine(start.x + 2, start.y, start.x + 2, end.y); + } + }; + + + public IndentsPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { + super(project, editor.getDocument(), false); + myEditor = (EditorEx)editor; + myFile = file; + } + + @Override + public void doCollectInformation(ProgressIndicator progress) { + } + + private long nowStamp() { + if (!myEditor.getSettings().isIndentGuidesShown()) return -1; + return myDocument.getModificationStamp(); + } + + @Override + public void doApplyInformationToEditor() { + final Long stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT); + if (stamp != null && stamp.longValue() == nowStamp()) return; + + List descriptors = buildDescriptors(); + + List ranges = new ArrayList(); + for (IndentGuideDescriptor descriptor : descriptors) { + int endOffset = descriptor.endLine < myDocument.getLineCount() ? myDocument.getLineStartOffset(descriptor.endLine) : myDocument.getTextLength(); + ranges.add(new TextRange(myDocument.getLineStartOffset(descriptor.startLine), endOffset)); + } + + + Collections.sort(ranges, RANGE_COMPARATOR); + List oldHighlighters = myEditor.getUserData(INDENT_HIGHLIGHTERS_IN_EDITOR_KEY); + List newHighlighters = new ArrayList(); + MarkupModel mm = myEditor.getMarkupModel(); + + int curRange = 0; + + if (oldHighlighters != null) { + int curHighlight = 0; + while (curRange < ranges.size() && curHighlight < oldHighlighters.size()) { + TextRange range = ranges.get(curRange); + RangeHighlighter highlighter = oldHighlighters.get(curHighlight); + + int cmp = compare(range, highlighter); + if (cmp < 0) { + newHighlighters.add(createHighlighter(mm, range)); + curRange++; + } + else if (cmp > 0) { + mm.removeHighlighter(highlighter); + curHighlight++; + } + else { + newHighlighters.add(highlighter); + curHighlight++; + curRange++; + } + } + + for (; curHighlight < oldHighlighters.size(); curHighlight++) { + mm.removeHighlighter(oldHighlighters.get(curHighlight)); + } + } + + for (; curRange < ranges.size(); curRange++) { + newHighlighters.add(createHighlighter(mm, ranges.get(curRange))); + } + + myEditor.putUserData(INDENT_HIGHLIGHTERS_IN_EDITOR_KEY, newHighlighters); + myEditor.putUserData(LAST_TIME_INDENTS_BUILT, nowStamp()); + myEditor.getIndentsModel().assumeIndents(descriptors); + } + + private List buildDescriptors() { + if (!myEditor.getSettings().isIndentGuidesShown()) return Collections.emptyList(); + + int[] lineIndents = calcIndents(myDocument); + + List descriptors = new ArrayList(); + + IntStack lines = new IntStack(); + IntStack indents = new IntStack(); + + lines.push(0); + indents.push(0); + for (int line = 1; line < lineIndents.length; line++) { + int curIndent = lineIndents[line]; + + while (!indents.empty() && curIndent <= indents.peek()) { + final int level = indents.pop(); + int startLine = lines.pop(); + descriptors.add(new IndentGuideDescriptor(level, startLine, line)); + } + + int prevLine = line - 1; + int prevIndent = lineIndents[prevLine]; + + if (curIndent - prevIndent > 1) { + lines.push(prevLine); + indents.push(prevIndent); + } + } + + while (!indents.empty()) { + final int level = indents.pop(); + if (level > 0) { + int startLine = lines.pop(); + descriptors.add(new IndentGuideDescriptor(level, startLine, myDocument.getLineCount())); + } + } + return descriptors; + } + + private int[] calcIndents(Document doc) { + CharSequence chars = doc.getCharsSequence(); + int[] lineIndents = new int[doc.getLineCount()]; + + for (int line = 0; line < lineIndents.length; line++) { + int lineStart = myDocument.getLineStartOffset(line); + int lineEnd = myDocument.getLineEndOffset(line); + + int nonWhitespaceOffset = CharArrayUtil.shiftForward(chars, lineStart, " \t"); + if (nonWhitespaceOffset < lineEnd) { + lineIndents[line] = myEditor.calcColumnNumber(nonWhitespaceOffset, line); + } + else { + lineIndents[line] = -1; + } + } + + int topIndent = 0; + for (int line = 0; line < lineIndents.length; line++) { + if (lineIndents[line] >= 0) { + topIndent = lineIndents[line]; + } + else { + int startLine = line; + for (; line < lineIndents.length && lineIndents[line] == -1; line++); + int bottomIndent = line < lineIndents.length ? lineIndents[line] : topIndent; + + int indent = Math.min(topIndent, bottomIndent); + if (bottomIndent < topIndent) { + int nonWhitespaceOffset = CharArrayUtil.shiftForward(chars, myDocument.getLineStartOffset(line), " \t"); + HighlighterIterator iterator = myEditor.getHighlighter().createIterator(nonWhitespaceOffset); + if (BraceMatchingUtil.isRBraceToken(iterator, chars, myFile.getFileType())) { + indent = topIndent; + } + } + + for (int blankLine = startLine; blankLine < line; blankLine++) { + assert lineIndents[blankLine] == -1; + lineIndents[blankLine] = Math.min(topIndent, indent); + } + + line--; // will be incremented back at the end of the loop; + } + } + + return lineIndents; + } + + private static RangeHighlighter createHighlighter(MarkupModel mm, TextRange range) { + final RangeHighlighter highlighter = + mm.addRangeHighlighter(range.getStartOffset(), range.getEndOffset(), 0, null, HighlighterTargetArea.EXACT_RANGE); + highlighter.setCustomRenderer(RENDERER); + return highlighter; + } + + private static int compare(TextRange r, RangeHighlighter h) { + int answer = r.getStartOffset() - h.getStartOffset(); + return answer != 0 ? answer : r.getEndOffset() - h.getEndOffset(); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPassFactory.java new file mode 100644 index 000000000000..61f732573eff --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPassFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.codeInsight.daemon.impl; + +import com.intellij.codeHighlighting.Pass; +import com.intellij.codeHighlighting.TextEditorHighlightingPass; +import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory; +import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar; +import com.intellij.openapi.components.AbstractProjectComponent; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +/** + * @author cdr +*/ +public class IndentsPassFactory extends AbstractProjectComponent implements TextEditorHighlightingPassFactory { + public IndentsPassFactory(Project project, TextEditorHighlightingPassRegistrar highlightingPassRegistrar) { + super(project); + highlightingPassRegistrar.registerTextEditorHighlightingPass(this, TextEditorHighlightingPassRegistrar.Anchor.BEFORE, Pass.UPDATE_FOLDING, false, false); + } + + @NonNls + @NotNull + public String getComponentName() { + return "IndentsPassFactory"; + } + + @NotNull + public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull final Editor editor) { + return new IndentsPass(myProject, editor, file); + } +} diff --git a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java index 180a8eb09d55..091d576f1d30 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java @@ -597,6 +597,10 @@ public class EditorWindow implements EditorEx, UserDataHolderEx { return myDelegate.calcColumnNumber(myDelegate.getDocument().getText(), hostStart, hostOffset, tabSize); } + public int calcColumnNumber(int offset, int lineIndex) { + return myDelegate.calcColumnNumber(myDocumentWindow.injectedToHost(offset), myDocumentWindow.injectedToHostLine(lineIndex)); + } + public IndentsModel getIndentsModel() { return myDelegate.getIndentsModel(); } diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java index 365b5e34e291..c7117d616122 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java @@ -26,10 +26,7 @@ package com.intellij.openapi.editor.actions; import com.intellij.codeInsight.editorActions.SelectWordUtil; import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.IndentGuideDescriptor; -import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.project.DumbAware; @@ -113,8 +110,8 @@ public class SelectWordAtCaretAction extends TextComponentEditorAction implement private static void selectWithGuide(Editor editor, IndentGuideDescriptor guide) { final Document doc = editor.getDocument(); - int startOffset = doc.getLineStartOffset(guide.startLine - 1); - int endOffset = Math.min(doc.getLineEndOffset(guide.endLine) + 1, doc.getTextLength()); + int startOffset = editor.logicalPositionToOffset(new LogicalPosition(guide.startLine, 0)); + int endOffset = Math.min(editor.logicalPositionToOffset(new LogicalPosition(guide.endLine + 1, 0)), doc.getTextLength()); editor.getSelectionModel().setSelection(startOffset, endOffset); } diff --git a/platform/platform-api/src/com/intellij/openapi/editor/IndentGuideDescriptor.java b/platform/platform-api/src/com/intellij/openapi/editor/IndentGuideDescriptor.java index cf087ba8a09b..9c49d6e86a48 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/IndentGuideDescriptor.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/IndentGuideDescriptor.java @@ -23,13 +23,11 @@ public class IndentGuideDescriptor { public final int indentLevel; public final int startLine; public final int endLine; - public int indentSize; - public IndentGuideDescriptor(int indentLevel, int startLine, int endLine, int indentSize) { + public IndentGuideDescriptor(int indentLevel, int startLine, int endLine) { this.indentLevel = indentLevel; this.startLine = startLine; this.endLine = endLine; - this.indentSize = indentSize; } @Override diff --git a/platform/platform-api/src/com/intellij/openapi/editor/IndentsModel.java b/platform/platform-api/src/com/intellij/openapi/editor/IndentsModel.java index 833af40042f6..38cd18fb8d4c 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/IndentsModel.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/IndentsModel.java @@ -21,12 +21,11 @@ package com.intellij.openapi.editor; import org.jetbrains.annotations.Nullable; +import java.util.List; + public interface IndentsModel { - int getIndentLevel(int line); - int getIndentSize(); - - void assumeIndent(int line, int level); - @Nullable IndentGuideDescriptor getCaretIndentGuide(); + + void assumeIndents(List descriptors); } diff --git a/platform/platform-api/src/com/intellij/openapi/editor/markup/CustomHighlighterRenderer.java b/platform/platform-api/src/com/intellij/openapi/editor/markup/CustomHighlighterRenderer.java new file mode 100644 index 000000000000..796235b9a89a --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/editor/markup/CustomHighlighterRenderer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author max + */ +package com.intellij.openapi.editor.markup; + +import com.intellij.openapi.editor.Editor; + +import java.awt.*; + +public interface CustomHighlighterRenderer { + void paint(Editor editor, RangeHighlighter highlighter, Graphics g); +} diff --git a/platform/platform-api/src/com/intellij/openapi/editor/markup/RangeHighlighter.java b/platform/platform-api/src/com/intellij/openapi/editor/markup/RangeHighlighter.java index 5e646c555e7b..ae5442f0c198 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/markup/RangeHighlighter.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/markup/RangeHighlighter.java @@ -79,6 +79,11 @@ public interface RangeHighlighter extends RangeMarker { */ void setLineMarkerRenderer(@Nullable LineMarkerRenderer renderer); + + @Nullable + CustomHighlighterRenderer getCustomRenderer(); + + void setCustomRenderer(CustomHighlighterRenderer renderer); /** * Returns the renderer used for drawing gutter icons in the area covered by the * highlighter. Gutter icons are drawn to the left of the folding area and can be used, @@ -199,4 +204,4 @@ public interface RangeHighlighter extends RangeMarker { */ @NotNull MarkupEditorFilter getEditorFilter(); -} \ No newline at end of file +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java index d5f9733d2824..b0cff4983683 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java @@ -121,4 +121,6 @@ public interface EditorEx extends Editor { VirtualFile getVirtualFile(); int calcColumnNumber(CharSequence text, int start, int offset, int tabSize); + + int calcColumnNumber(int offset, int lineIndex); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 4798d8686692..66ae17517baf 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -117,7 +117,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi ComplementaryFontsRegistry registry; // load costly font info } - private final CommandProcessor myCommandProcessor; + private final CommandProcessor myCommandProcessor; private final MyScrollBar myVerticalScrollBar; private final CopyOnWriteArrayList myMouseListeners = ContainerUtil.createEmptyCOWList(); @@ -223,7 +223,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private char[] myPrefixText; private TextAttributes myPrefixAttributes; - private IndentGuideDescriptor myCaretIndentGuide = null; private IndentsModel myIndentsModel; static { @@ -285,15 +284,15 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myIndentsModel = new IndentsModelImpl(this); myCaretModel.addCaretListener(new CaretListener() { - LightweightHint myCurrentHint = null; + private LightweightHint myCurrentHint = null; + private IndentGuideDescriptor myCurrentCaretGuide = null; public void caretPositionChanged(CaretEvent e) { - final IndentGuideDescriptor newGuide = myIndentsModel.getCaretIndentGuide(); - if (!Comparing.equal(newGuide, myCaretIndentGuide)) { + if (!Comparing.equal(myCurrentCaretGuide, newGuide)) { repaintGuide(newGuide); - repaintGuide(myCaretIndentGuide); - myCaretIndentGuide = newGuide; + repaintGuide(myCurrentCaretGuide); + myCurrentCaretGuide = newGuide; if (myCurrentHint != null) { myCurrentHint.hide(); @@ -302,11 +301,10 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi if (newGuide != null) { final Rectangle visibleArea = getScrollingModel().getVisibleArea(); - final int line = newGuide.startLine - 1; + final int line = newGuide.startLine; if (logicalLineToY(line) < visibleArea.y) { - TextRange textRange = new TextRange(myDocument.getLineStartOffset(line), - myDocument.getLineEndOffset(line)); - + TextRange textRange = new TextRange(myDocument.getLineStartOffset(line), myDocument.getLineEndOffset(line)); + myCurrentHint = EditorFragmentComponent.showEditorFragmentHint(EditorImpl.this, textRange, false, false); } } @@ -1208,7 +1206,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi paintBackgrounds(g, clip); paintRectangularSelection(g); paintRightMargin(g, clip); - paintIndentGuides((Graphics2D)g, clip); + paintCustomRenderers((Graphics2D)g, clip); final MarkupModel docMarkup = myDocument.getMarkupModel(myProject); paintLineMarkersSeparators(g, clip, docMarkup); paintLineMarkersSeparators(g, clip, myMarkupModel); @@ -1223,76 +1221,19 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi paintComposedTextDecoration((Graphics2D)g); } - private void paintIndentGuides(Graphics2D g, Rectangle clip) { - if (mySettings.isIndentGuidesShown()) { - int x = 0; - int y = clip.y; - int line = xyToLogicalPosition(new Point(0, y)).line; + private void paintCustomRenderers(Graphics2D g, Rectangle clip) { + MarkupModel mm = myMarkupModel; - final int indentSize = myIndentsModel.getIndentSize(); - int gapWidth = EditorUtil.getSpaceWidth(Font.PLAIN, this) * indentSize; - final CharSequence chars = myDocument.getCharsNoThreadCheck(); + int clipStartOffset = logicalPositionToOffset(xyToLogicalPosition(new Point(0, clip.y))); + int clipEndOffset = logicalPositionToOffset(xyToLogicalPosition(new Point(0, clip.y + clip.height + getLineHeight()))); - int prevIndent = -1; - do { - final int indents = myIndentsModel.getIndentLevel(line); - - /* - if (prevIndent > indents && isWhitespaceAt(chars, x, y)) { - g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR)); - g.drawLine(x, y + getLineHeight() - 2, x + 7, y + getLineHeight() - 2); + for (RangeHighlighter highlighter : mm.getAllHighlighters()) { + if (highlighter.isValid()) { + final CustomHighlighterRenderer customRenderer = highlighter.getCustomRenderer(); + if (customRenderer != null && clipStartOffset < highlighter.getEndOffset() && clipEndOffset > highlighter.getStartOffset()) { + customRenderer.paint(this, highlighter, g); } - */ - - y = logicalLineToY(line); - if (y > clip.y + clip.height) break; - - for (int n = 1; n < indents; n++) { - x = n * gapWidth + 1; - drawSegment(g, x, y, chars, false); - } - - line++; - prevIndent = indents; } - while (true); - - if (myCaretIndentGuide != null) { - x = myCaretIndentGuide.indentLevel * gapWidth + 1; - for (int i = myCaretIndentGuide.startLine; i <= myCaretIndentGuide.endLine; i++) { - drawSegment(g, x, logicalLineToY(i), chars, true); - } - - /* - y = logicalLineToY(myCaretIndentGuide.endLine); - if (isWhitespaceAt(chars, x, y)) { - g.setColor(getForegroundColor()); - g.drawLine(x, y + getLineHeight() - 2, x + 7, y + getLineHeight() - 2); - } - */ - } - } - } - - private boolean isWhitespaceAt(CharSequence chars, int x, int y) { - LogicalPosition log = xyToLogicalPosition(new Point(x, y)); - int offset = logicalPositionToOffset(log); - if (offset >= chars.length()) return false; - - char c = chars.charAt(offset); - return c == ' ' || c == '\t' || c == '\n'; - } - - private void drawSegment(Graphics2D g, int x, int y, CharSequence chars, boolean selected) { - if (isWhitespaceAt(chars, x, y)) { - /* - UIUtil.drawDottedLine(g, x, y, x, y + getLineHeight(), - getBackroundColor(), - selected ? getForegroundColor() : myScheme.getColor(EditorColors.WHITESPACES_COLOR)); - */ - - g.setColor(selected ? new Color(200, 200, 200) : new Color(230, 230, 230)); - g.drawLine( x + 1, y, x + 1, y + getLineHeight()); } } @@ -2405,7 +2346,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return lineIndex; } - int calcColumnNumber(int offset, int lineIndex) { + public int calcColumnNumber(int offset, int lineIndex) { if (myDocument.getTextLength() == 0) return 0; CharSequence text = myDocument.getCharsSequence(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyIndentsModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyIndentsModel.java index 7148ffa9641c..99de9b6afa6a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyIndentsModel.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyIndentsModel.java @@ -22,19 +22,14 @@ package com.intellij.openapi.editor.impl; import com.intellij.openapi.editor.IndentGuideDescriptor; import com.intellij.openapi.editor.IndentsModel; +import java.util.List; + public class EmptyIndentsModel implements IndentsModel { - public int getIndentLevel(int line) { - return 0; - } - - public int getIndentSize() { - return 4; - } - - public void assumeIndent(int line, int level) { - } public IndentGuideDescriptor getCaretIndentGuide() { return null; } + + public void assumeIndents(List descriptors) { + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java index 57e235096282..ddfa1218e4c3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IndentsModelImpl.java @@ -19,106 +19,33 @@ */ package com.intellij.openapi.editor.impl; -import com.intellij.codeStyle.CodeStyleFacade; -import com.intellij.openapi.editor.EditorSettings; -import com.intellij.openapi.editor.IndentGuideDescriptor; -import com.intellij.openapi.editor.IndentsModel; -import com.intellij.openapi.editor.LogicalPosition; -import com.intellij.openapi.editor.ex.util.EditorUtil; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.text.CharArrayUtil; -import gnu.trove.TIntArrayList; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.*; + +import java.util.ArrayList; +import java.util.List; public class IndentsModelImpl implements IndentsModel { private EditorImpl myEditor; - private int myIndentSize = -1; + private List myIndents = new ArrayList(); public IndentsModelImpl(EditorImpl editor) { myEditor = editor; } - public int getIndentLevel(int line) { - int answer = getIndents(line, true, true); - if (answer == 0) return 0; - - int prev; - do { - prev = getIndents(--line, true, false); - } - while (line > 0 && prev == answer); - - if (answer - 2 > prev) { - return prev + 1; - } - - return answer; - } - - private int getIndents(int line, boolean goUp, boolean goDown) { - DocumentImpl myDocument = (DocumentImpl)myEditor.getDocument(); - if (line <= 0 || line >= myDocument.getLineCount()) return 0; - int lineStart = myDocument.getLineStartOffset(line); - int lineEnd = myDocument.getLineEndOffset(line); - - CharSequence chars = myDocument.getCharsNoThreadCheck(); - int nonWhitespaceOffset = CharArrayUtil.shiftForward(chars, lineStart, " \t"); - if (nonWhitespaceOffset < lineEnd) { - return myEditor.calcColumnNumber(nonWhitespaceOffset, line) / getIndentSize(); - } - else { - int upIndent = goUp ? getIndents(line - 1, true, false) : 100; - int downIndent = goDown ? getIndents(line + 1, false, true) : 100; - return Math.min(upIndent, downIndent); - } - } - - public int getIndentSize() { - if (myIndentSize == -1) { - Project project = myEditor.getProject(); - VirtualFile vFile = myEditor.getVirtualFile(); - if (project == null || project.isDisposed() || vFile == null) return EditorUtil.getTabSize(myEditor); - myIndentSize = CodeStyleFacade.getInstance(project).getIndentSize(vFile.getFileType()); - } - return myIndentSize; - } - - public void assumeIndent(int line, int level) { - throw new UnsupportedOperationException("assumeIndent is not implemented"); // TODO - } - public IndentGuideDescriptor getCaretIndentGuide() { - EditorSettings settings = myEditor.getSettings(); - if (!settings.isIndentGuidesShown()) return null; - - final int indentSize = getIndentSize(); - if (indentSize == 0) return null; - - final LogicalPosition caretPosition = myEditor.getCaretModel().getLogicalPosition(); - int startLine = caretPosition.line; - int endLine = startLine; - final int caretIndent = caretPosition.column / indentSize; - final int indents = getIndentLevel(startLine); - - if (caretIndent * indentSize != caretPosition.column) return null; - if (caretIndent > indents|| indents == 0) return null; - - if (caretIndent > 0 && caretPosition.column % indentSize == 0) { - while (startLine > 0) { - if (getIndentLevel(startLine - 1) <= caretIndent) break; - startLine--; - } - - while (endLine < myEditor.getDocument().getLineCount() - 1) { - if (getIndentLevel(endLine + 1) <= caretIndent) break; - endLine++; - } - - if (startLine < endLine) { - return new IndentGuideDescriptor(caretIndent, startLine, endLine, indentSize); + final LogicalPosition pos = myEditor.getCaretModel().getLogicalPosition(); + final int column = pos.column; + final int line = pos.line; + for (IndentGuideDescriptor indent : myIndents) { + if (column == indent.indentLevel && line >= indent.startLine && line < indent.endLine) { + return indent; } } - return null; } + + public void assumeIndents(List descriptors) { + myIndents = descriptors; + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java index b0ea3dfa2d35..d577c9de4134 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java @@ -46,6 +46,7 @@ public class RangeHighlighterImpl implements RangeHighlighterEx { private boolean myErrorStripeMarkIsThin; private Object myErrorStripeTooltip; private MarkupEditorFilter myFilter = MarkupEditorFilter.EMPTY; + private CustomHighlighterRenderer myCustomRenderer; RangeHighlighterImpl(@NotNull MarkupModel model, int start, @@ -110,6 +111,14 @@ public class RangeHighlighterImpl implements RangeHighlighterEx { fireChanged(); } + public CustomHighlighterRenderer getCustomRenderer() { + return myCustomRenderer; + } + + public void setCustomRenderer(CustomHighlighterRenderer renderer) { + myCustomRenderer = renderer; + } + public GutterIconRenderer getGutterIconRenderer() { return myGutterIconRenderer; } diff --git a/platform/platform-resources/src/componentSets/Lang.xml b/platform/platform-resources/src/componentSets/Lang.xml index 7daa12e4c568..18d10636ffbd 100644 --- a/platform/platform-resources/src/componentSets/Lang.xml +++ b/platform/platform-resources/src/componentSets/Lang.xml @@ -151,6 +151,10 @@ com.intellij.codeInsight.daemon.impl.CodeFoldingPassFactory + + com.intellij.codeInsight.daemon.impl.IndentsPassFactory + + com.intellij.codeInsight.daemon.impl.InjectedCodeFoldingPassFactory diff --git a/platform/util/src/com/intellij/util/containers/IntStack.java b/platform/util/src/com/intellij/util/containers/IntStack.java new file mode 100644 index 000000000000..eacf5932b619 --- /dev/null +++ b/platform/util/src/com/intellij/util/containers/IntStack.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author max + */ +package com.intellij.util.containers; + +import com.intellij.util.ArrayUtil; + +import java.util.ArrayList; +import java.util.EmptyStackException; +import java.util.List; +import java.util.RandomAccess; + +public class IntStack { + private int[] data; + private int size; + public IntStack(int initialCapacity) { + data = new int[initialCapacity]; + size = 0; + } + + public IntStack() { + this(5); + } + + public void push(int t) { + if (size >= data.length) { + int[] newdata = new int[data.length * 3 / 2]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = t; + } + + public int peek() { + if (size == 0) throw new EmptyStackException(); + return data[size - 1]; + } + + public int pop() { + if (size == 0) throw new EmptyStackException(); + return data[--size]; + } + + public boolean empty() { + return size == 0; + } + + @Override + public boolean equals(Object o) { + if (o instanceof IntStack) { + IntStack otherStack = (IntStack)o; + if (size != otherStack.size) return false; + for (int i = 0; i < otherStack.size; i++) { + if (data[i] != otherStack.data[i]) return false; + } + return true; + } + + return false; + } +} From bc02158fe2b7d474d9656296ee06d3dabea31571 Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Thu, 11 Mar 2010 19:51:21 +0300 Subject: [PATCH 11/71] IDEA-21206 "Usages of" ignore "Show import statements" --- .../impl/rules/ImportFilteringRule.java | 5 ++-- .../src/META-INF/LangExtensionPoints.xml | 2 ++ .../impl/ImportFilteringUsageViewSetting.java | 14 +++++----- .../ImportUsageFilteringRuleProvider.java | 18 ++++++------ .../usages/rules/ImportFilteringRule.java | 28 +++++++++++++++++++ resources/src/META-INF/IdeaPlugin.xml | 7 +++-- 6 files changed, 52 insertions(+), 22 deletions(-) rename java/java-impl/src/com/intellij/usages/impl/rules/JavaUsageViewSettings.java => platform/usageView/src/com/intellij/usages/impl/ImportFilteringUsageViewSetting.java (70%) rename java/java-impl/src/com/intellij/usages/impl/rules/JavaUsageFilteringRuleProvider.java => platform/usageView/src/com/intellij/usages/impl/ImportUsageFilteringRuleProvider.java (82%) create mode 100644 platform/usageView/src/com/intellij/usages/rules/ImportFilteringRule.java diff --git a/java/java-impl/src/com/intellij/usages/impl/rules/ImportFilteringRule.java b/java/java-impl/src/com/intellij/usages/impl/rules/ImportFilteringRule.java index f8b67400ed11..4f9f7a931ceb 100644 --- a/java/java-impl/src/com/intellij/usages/impl/rules/ImportFilteringRule.java +++ b/java/java-impl/src/com/intellij/usages/impl/rules/ImportFilteringRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2010 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,12 @@ import com.intellij.psi.PsiJavaFile; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.usages.Usage; import com.intellij.usages.rules.PsiElementUsage; -import com.intellij.usages.rules.UsageFilteringRule; /** * @author Eugene Zhuravlev * Date: Jan 17, 2005 */ -public class ImportFilteringRule implements UsageFilteringRule{ +public class ImportFilteringRule extends com.intellij.usages.rules.ImportFilteringRule { public boolean isVisible(Usage usage) { if (usage instanceof PsiElementUsage) { final PsiElement psiElement = ((PsiElementUsage)usage).getElement(); diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index 51dc6c039dbf..ef80aa6699b6 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -313,6 +313,8 @@ + { - public static JavaUsageViewSettings getInstance() { - return ServiceManager.getService(JavaUsageViewSettings.class); +public class ImportFilteringUsageViewSetting implements PersistentStateComponent { + public static ImportFilteringUsageViewSetting getInstance() { + return ServiceManager.getService(ImportFilteringUsageViewSetting.class); } public boolean SHOW_IMPORTS = true; - public JavaUsageViewSettings getState() { + public ImportFilteringUsageViewSetting getState() { return this; } - public void loadState(final JavaUsageViewSettings state) { + public void loadState(final ImportFilteringUsageViewSetting state) { XmlSerializerUtil.copyBean(state, this); } } diff --git a/java/java-impl/src/com/intellij/usages/impl/rules/JavaUsageFilteringRuleProvider.java b/platform/usageView/src/com/intellij/usages/impl/ImportUsageFilteringRuleProvider.java similarity index 82% rename from java/java-impl/src/com/intellij/usages/impl/rules/JavaUsageFilteringRuleProvider.java rename to platform/usageView/src/com/intellij/usages/impl/ImportUsageFilteringRuleProvider.java index 08760947db0b..3920bdf28cff 100644 --- a/java/java-impl/src/com/intellij/usages/impl/rules/JavaUsageFilteringRuleProvider.java +++ b/platform/usageView/src/com/intellij/usages/impl/ImportUsageFilteringRuleProvider.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.usages.impl.rules; +package com.intellij.usages.impl; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.IconLoader; import com.intellij.usageView.UsageViewBundle; +import com.intellij.usages.rules.ImportFilteringRule; import com.intellij.usages.UsageView; -import com.intellij.usages.impl.RuleAction; -import com.intellij.usages.impl.UsageViewImpl; import com.intellij.usages.rules.UsageFilteringRule; import com.intellij.usages.rules.UsageFilteringRuleProvider; import org.jetbrains.annotations.NotNull; @@ -32,17 +32,18 @@ import javax.swing.*; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** * @author yole */ -public class JavaUsageFilteringRuleProvider implements UsageFilteringRuleProvider { +public class ImportUsageFilteringRuleProvider implements UsageFilteringRuleProvider { @NotNull public UsageFilteringRule[] getActiveRules(@NotNull final Project project) { final List rules = new ArrayList(); - if (!JavaUsageViewSettings.getInstance().SHOW_IMPORTS) { - rules.add(new ImportFilteringRule()); + if (!ImportFilteringUsageViewSetting.getInstance().SHOW_IMPORTS) { + rules.addAll(Arrays.asList(Extensions.getExtensions(ImportFilteringRule.EP_NAME))); } return rules.toArray(new UsageFilteringRule[rules.size()]); } @@ -73,12 +74,11 @@ public class JavaUsageFilteringRuleProvider implements UsageFilteringRuleProvide } protected boolean getOptionValue() { - return JavaUsageViewSettings.getInstance().SHOW_IMPORTS; + return ImportFilteringUsageViewSetting.getInstance().SHOW_IMPORTS; } protected void setOptionValue(boolean value) { - JavaUsageViewSettings.getInstance().SHOW_IMPORTS = value; + ImportFilteringUsageViewSetting.getInstance().SHOW_IMPORTS = value; } } - } diff --git a/platform/usageView/src/com/intellij/usages/rules/ImportFilteringRule.java b/platform/usageView/src/com/intellij/usages/rules/ImportFilteringRule.java new file mode 100644 index 000000000000..5ab8a7da65c6 --- /dev/null +++ b/platform/usageView/src/com/intellij/usages/rules/ImportFilteringRule.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.usages.rules; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.usages.rules.UsageFilteringRule; + +/** + * User: Maxim.Mossienko + * Date: 11.03.2010 + * Time: 19:08:33 + */ +public abstract class ImportFilteringRule implements UsageFilteringRule { + public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.importFilteringRule"); +} diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 4e524219c134..7207661d2784 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -1036,9 +1036,10 @@ - - + + + From 7e3a238cfa1d68abe99af6aac3cd84aa30736844 Mon Sep 17 00:00:00 2001 From: Maxim Shafirov Date: Thu, 11 Mar 2010 21:17:59 +0300 Subject: [PATCH 12/71] Configurable colors for vertical indent lines. --- .../com/intellij/codeInsight/daemon/impl/IndentsPass.java | 5 ++++- .../openapi/options/colors/pages/GeneralColorsPage.java | 2 ++ .../src/com/intellij/openapi/editor/colors/EditorColors.java | 2 ++ .../src/messages/OptionsBundle.properties | 2 ++ .../platform-resources/src/DefaultColorSchemesManager.xml | 2 ++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java index 681edde54672..221f2dd4e26b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java @@ -22,6 +22,8 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeHighlighting.TextEditorHighlightingPass; import com.intellij.codeInsight.highlighting.BraceMatchingUtil; import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.editor.markup.CustomHighlighterRenderer; @@ -98,7 +100,8 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware Point start = editor.visualPositionToXY(new VisualPosition(startPosition.line + 1, startPosition.column)); final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset); Point end = editor.visualPositionToXY(new VisualPosition(endPosition.line, endPosition.column)); - g.setColor(selected ? new Color(200, 200, 200) : new Color(230, 230, 230)); + final EditorColorsScheme scheme = editor.getColorsScheme(); + g.setColor(selected ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR) : scheme.getColor(EditorColors.INDENT_GUIDE_COLOR)); g.drawLine(start.x + 2, start.y, start.x + 2, end.y); } }; diff --git a/platform/lang-impl/src/com/intellij/openapi/options/colors/pages/GeneralColorsPage.java b/platform/lang-impl/src/com/intellij/openapi/options/colors/pages/GeneralColorsPage.java index eff054456b4f..0f72d83b9c20 100644 --- a/platform/lang-impl/src/com/intellij/openapi/options/colors/pages/GeneralColorsPage.java +++ b/platform/lang-impl/src/com/intellij/openapi/options/colors/pages/GeneralColorsPage.java @@ -93,6 +93,8 @@ public class GeneralColorsPage implements ColorSettingsPage { new ColorDescriptor(OptionsBundle.message("options.general.color.descriptor.caret.row"), EditorColors.CARET_ROW_COLOR, ColorDescriptor.Kind.BACKGROUND), new ColorDescriptor(OptionsBundle.message("options.general.color.descriptor.right.margin"), EditorColors.RIGHT_MARGIN_COLOR, ColorDescriptor.Kind.FOREGROUND), new ColorDescriptor(OptionsBundle.message("options.general.color.descriptor.whitespaces"), EditorColors.WHITESPACES_COLOR, ColorDescriptor.Kind.BACKGROUND), + new ColorDescriptor(OptionsBundle.message("options.general.color.descriptor.indent.guide"), EditorColors.INDENT_GUIDE_COLOR, ColorDescriptor.Kind.BACKGROUND), + new ColorDescriptor(OptionsBundle.message("options.general.color.descriptor.indent.guide.selected"), EditorColors.SELECTED_INDENT_GUIDE_COLOR, ColorDescriptor.Kind.BACKGROUND), new ColorDescriptor(OptionsBundle.message("options.general.color.descriptor.line.number"), EditorColors.LINE_NUMBERS_COLOR, ColorDescriptor.Kind.FOREGROUND), new ColorDescriptor(OptionsBundle.message("options.general.color.descriptor.vcs.annotations"), EditorColors.ANNOTATIONS_COLOR, ColorDescriptor.Kind.FOREGROUND), new ColorDescriptor(OptionsBundle.message("options.general.color.descriptor.vcs.annotations.merged"), EditorColors.ANNOTATIONS_MERGED_COLOR, ColorDescriptor.Kind.FOREGROUND), diff --git a/platform/platform-api/src/com/intellij/openapi/editor/colors/EditorColors.java b/platform/platform-api/src/com/intellij/openapi/editor/colors/EditorColors.java index 3995365e78d9..09d8b21080b2 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/colors/EditorColors.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/colors/EditorColors.java @@ -25,6 +25,8 @@ public interface EditorColors { ColorKey READONLY_BACKGROUND_COLOR = ColorKey.createColorKey("READONLY_BACKGROUND"); ColorKey READONLY_FRAGMENT_BACKGROUND_COLOR = ColorKey.createColorKey("READONLY_FRAGMENT_BACKGROUND"); ColorKey WHITESPACES_COLOR = ColorKey.createColorKey("WHITESPACES"); + ColorKey INDENT_GUIDE_COLOR = ColorKey.createColorKey("INDENT_GUIDE"); + ColorKey SELECTED_INDENT_GUIDE_COLOR = ColorKey.createColorKey("SELECTED_INDENT_GUIDE"); ColorKey SELECTION_BACKGROUND_COLOR = ColorKey.createColorKey("SELECTION_BACKGROUND"); ColorKey SELECTION_FOREGROUND_COLOR = ColorKey.createColorKey("SELECTION_FOREGROUND"); diff --git a/platform/platform-resources-en/src/messages/OptionsBundle.properties b/platform/platform-resources-en/src/messages/OptionsBundle.properties index 19446498a094..c207d61fd61a 100644 --- a/platform/platform-resources-en/src/messages/OptionsBundle.properties +++ b/platform/platform-resources-en/src/messages/OptionsBundle.properties @@ -113,6 +113,8 @@ options.general.color.descriptor.caret=Caret options.general.color.descriptor.caret.row=Caret row options.general.color.descriptor.right.margin=Right margin options.general.color.descriptor.whitespaces=Whitespaces +options.general.color.descriptor.indent.guide=Vertical indent guide +options.general.color.descriptor.indent.guide.selected=Selected vertical indent guide options.general.color.descriptor.line.number=Line number options.general.color.descriptor.vcs.annotations=VCS annotations options.general.color.descriptor.vcs.annotations.merged=VCS annotations (merged from) diff --git a/platform/platform-resources/src/DefaultColorSchemesManager.xml b/platform/platform-resources/src/DefaultColorSchemesManager.xml index ca800ccad6fa..73e39badc00b 100644 --- a/platform/platform-resources/src/DefaultColorSchemesManager.xml +++ b/platform/platform-resources/src/DefaultColorSchemesManager.xml @@ -10,6 +10,8 @@