From 27540c4021e262377cd918d32c7a38a3d0af255c Mon Sep 17 00:00:00 2001 From: Birgi Tamersoy <> Date: Sat, 11 Oct 2008 19:47:40 +0200 Subject: [PATCH] add on-the-fly code checking Adds a code checker that runs in the background and highlights lines with syntax errors. Currently it only works on Linux with Pthreads and gcc/g++ as compiler. 2008 Aug 18 http://codecheck.googlecode.com/svn/trunk/code_check_v0.1.patch http://groups.google.com/group/vim_dev/browse_thread/thread/1f3a7e132d9577af/bd270f161e0a2e15 --- runtime/doc/code_check.txt | 62 +++ runtime/doc/index.txt | 2 + runtime/doc/tags | 5 + src/Makefile | 10 + src/code_check.c | 1063 ++++++++++++++++++++++++++++++++++++++++++++ src/ex_cmds.h | 4 + src/ex_docmd.c | 29 ++ src/fileio.c | 6 + src/getchar.c | 14 + src/keymap.h | 3 + src/misc1.c | 5 + src/screen.c | 20 + 12 files changed, 1223 insertions(+) create mode 100644 runtime/doc/code_check.txt create mode 100644 src/code_check.c diff --git a/runtime/doc/code_check.txt b/runtime/doc/code_check.txt new file mode 100644 index 00000000..4faa8bfc --- /dev/null +++ b/runtime/doc/code_check.txt @@ -0,0 +1,62 @@ +*code_check.txt* For Vim version 7.2. Last change: 2008 Aug 18 + + + VIM REFERENCE MANUAL by Bram Moolenaar + + +CodeCheck [version 0.1] *codecheck* *CodeCheck* *code_check* + +1. What is CodeCheck |intro| +2. Using :ccadd |ccadd| +3. Using :ccrem |ccrem| +4. Progress |progress| + +============================================================================== +1. What is CodeCheck *intro* + +*** CodeCheck is sponsored by Google under Google Summer of Code program, +mentored by Bram Moolenaar and implemented by Birgi Tamersoy. *** + +CodeCheck is an on-the-fly syntax checking tool implemented for Vim. It mimics +the similar feature in Eclipse IDE. Whenever the user delays for a specified +amount of time, a background thread: + - creates a temporary copy of the current buffer, + - compiles this temporary copy, + - parses the compiler output, and + - highlights the related lines in the buffer. + +As of today (last change date), CodeCheck is highly platform-dependent. It +will work only on Linux with Pthreads. Current supported file types are; C/C++ +independent source files. + +============================================================================== +2. Using :ccadd *ccadd* + +Usage: +:ccadd Adds current buffer to the CodeCheck + watchlist. is required. + must include the full path + of the corresponding buffer. + +Example: +:ccadd gcc -Wall -c /home/birgi/tmp/a/b/main.c + +============================================================================== +3. Using :ccrem *ccrem* + +Usage: +:ccrem Removes current buffer from the CodeCheck + watchlist. This command does not take any + arguments. If the buffer is not being watched, + the command is simply ignored. + +============================================================================== +4. Progress *progress* + +As of today (last change date) the maintainer works on the following issues: +- Porting to Windows. +- Specific parts highlighting rather than line highlighting. +- Implementing a mechanism that would make new compiler/language support + easier. + +============================================================================== diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index afe15136..277f6123 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1108,7 +1108,9 @@ The commands are sorted on the non-optional part of their name. |:catch| :cat[ch] part of a :try command |:cbuffer| :cb[uffer] parse error messages and jump to first error |:cc| :cc go to specific error +|:ccadd| :ccadd add current buffer to CodeCheck watchlist |:cclose| :ccl[ose] close quickfix window +|:ccrem| :ccrem remove current buffer from CodeCheck watchlist |:cd| :cd change directory |:center| :ce[nter] format lines at the center |:cexpr| :cex[pr] read errors from expr and jump to first diff --git a/runtime/doc/tags b/runtime/doc/tags index cf872e7a..d8c7b7f0 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -1898,8 +1898,10 @@ $VIMRUNTIME starting.txt /*$VIMRUNTIME* :cb quickfix.txt /*:cb* :cbuffer quickfix.txt /*:cbuffer* :cc quickfix.txt /*:cc* +:ccadd code_check.txt /*:ccadd* :ccl quickfix.txt /*:ccl* :cclose quickfix.txt /*:cclose* +:ccrem code_check.txt /*:ccrem* :cd editing.txt /*:cd* :cd- editing.txt /*:cd-* :ce change.txt /*:ce* @@ -3192,6 +3194,7 @@ BufWritePre autocmd.txt /*BufWritePre* C change.txt /*C* C-editing tips.txt /*C-editing* C-indenting indent.txt /*C-indenting* +CodeCheck code_check.txt /*CodeCheck* COMSPEC starting.txt /*COMSPEC* CR-used-for-NL pattern.txt /*CR-used-for-NL* CTRL-6 editing.txt /*CTRL-6* @@ -4829,6 +4832,8 @@ cmdwin cmdline.txt /*cmdwin* cmdwin-char cmdline.txt /*cmdwin-char* cobol.vim syntax.txt /*cobol.vim* codeset mbyte.txt /*codeset* +codecheck code_check.txt /*codecheck* +code_check code_check.txt /*code_check* coding-style develop.txt /*coding-style* col() eval.txt /*col()* coldfusion.vim syntax.txt /*coldfusion.vim* diff --git a/src/Makefile b/src/Makefile index e27a6ce0..e4f6ce7d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1366,6 +1366,7 @@ TAGS_INCL = *.h BASIC_SRC = \ buffer.c \ charset.c \ + code_check.c \ diff.c \ digraph.c \ edit.c \ @@ -1436,6 +1437,7 @@ LINT_SRC = $(BASIC_SRC) $(GUI_SRC) $(HANGULIN_SRC) $(PYTHON_SRC) $(TCL_SRC) \ OBJ = \ objects/buffer.o \ objects/charset.o \ + objects/code_check.o \ objects/diff.o \ objects/digraph.o \ objects/edit.o \ @@ -1495,6 +1497,7 @@ OBJ = \ PRO_AUTO = \ buffer.pro \ charset.pro \ + code_check.pro \ diff.pro \ digraph.pro \ edit.pro \ @@ -2327,6 +2330,9 @@ objects/buffer.o: buffer.c objects/charset.o: charset.c $(CCC) -o $@ charset.c +objects/code_check.o: code_check.c + $(CCC) -o $@ code_check.c + objects/diff.o: diff.c $(CCC) -o $@ diff.c @@ -2655,6 +2661,10 @@ objects/charset.o: charset.c vim.h auto/config.h feature.h os_unix.h auto/osdef. ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \ gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h globals.h farsi.h \ arabic.h +objects/code_check.o: code_check.c vim.h auto/config.h feature.h os_unix.h auto/osdef.h \ + ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \ + gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h globals.h farsi.h \ + arabic.h objects/diff.o: diff.c vim.h auto/config.h feature.h os_unix.h auto/osdef.h \ ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \ gui_beval.h proto/gui_beval.pro ex_cmds.h proto.h globals.h farsi.h \ diff --git a/src/code_check.c b/src/code_check.c new file mode 100644 index 00000000..5bb3bfe0 --- /dev/null +++ b/src/code_check.c @@ -0,0 +1,1063 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * CodeCheck extension by Birgi Tamersoy + * birgitamersoy@gmail.com + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * code_check.c: On-the-fly syntax checking tool. + */ + +#include +#include +#include "vim.h" + +#define CC_VERSION 0.1 + +enum cc_ret_vals { + CC_FAIL = 0, + CC_SUCCESS, + CC_BUFEXISTS, + CC_NOSUCHBUF, +}; + +enum cc_ew_types { + CC_NOEW = 0, + CC_WARNING, + CC_ERROR +}; + +enum cc_search_mode { + CC_FOR_ADD = 0, + CC_FOR_REM, + CC_FOR_FIND +}; + +enum cc_print_direc { + CC_FWD = 0, + CC_REW +}; + +enum cc_copy_type { + CC_STANDALONE = 0, + CC_MASTER, + CC_PROJECT /* Makefile exists */ +}; + +/* + * Multiple buffers can be syntax checked simultaneously and each buffer + * has its own list of errors and warnings. Consequently we have two list + * structures. One holds information about processed buffers and the other + * one holds information about buffer-specific errors & warnings. + * + * Both lists will be accessed frequently, so they will be sorted: + * buffer list will be sorted with respect to full file names, and + * error/warning list will be sorted with respect to line numbers. + */ + +#define MAX_EW_TEXT 200 + +/* compile command length */ +#define MAX_CMD_LENGTH 600 + +#define MAX_PATH_LENGTH 300 + +typedef struct cc_ewline_S cc_ewline_T; +struct cc_ewline_S { + cc_ewline_T *prev; + cc_ewline_T *next; + int ew_type; + char_u ew_text[MAX_EW_TEXT]; + linenr_T ew_lnum; +}; + +typedef struct cc_bufline_S cc_bufline_T; +struct cc_bufline_S { + cc_bufline_T *prev; + cc_bufline_T *next; + char_u *buf_name; + cc_ewline_T *buf_ewlist_head; + pthread_mutex_t buf_mutex; + char_u buf_compile_cmd[MAX_CMD_LENGTH]; +}; + +typedef struct cc_info_S cc_info_T; +struct cc_info_S { + int cc_bufcount; + cc_bufline_T *cc_list_head; + cc_bufline_T *cc_list_curr; +}; + +/* global list of buffers. */ +static cc_info_T cc_list; +#define MAX_BUFLINES 100 +static cc_bufline_T *cc_bufline_ptrs[MAX_BUFLINES]; + +/* supported languages, the format should be "." ... */ +/* each extension should padded with ' ' characters, st. total is 5 chars */ +static char_u *cc_sup_exts = (char_u *) ".c " + ".cpp "; + +/* worker thread. */ +static pthread_t cc_slave; + +/* a queue for the pending jobs that the worker thread should handle. + * basically a list of buffer compile requests. */ +enum cc_pjob_types { + CC_COMPILE = 0 +}; + +typedef struct cc_pjob_S cc_pjob_T; +struct cc_pjob_S { + int cc_pjob_type; + buf_T *cc_pjob_buf; +}; + +#define MAX_PENDING_JOBS 10 +static cc_pjob_T *cc_pjobs[MAX_PENDING_JOBS]; +static int pindex = 0; +static int cindex = 0; + +static sem_t full; +static sem_t empty; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +/* functions */ +static void cc_free_ewlist(cc_ewline_T *ewlist_head); +static cc_bufline_T *cc_locate_buf(buf_T *buf); +static cc_bufline_T *cc_locate_buf_bin(buf_T *buf, int *buf_idx, + int *put_before, int mode); +static void cc_free_buflist(cc_info_T *cc_list_a, + cc_bufline_T *cc_bufline_ptrs_a[MAX_BUFLINES]); +static int cc_start_slave_thread(void); +static void *cc_slave_sroutine(void *args); +static int cc_pjobs_produce(cc_pjob_T *tmp_pjob); +static cc_pjob_T *cc_pjobs_consume(void); +static int cc_pjobs_buf_exists(cc_pjob_T *tmp_pjob); +static int cc_create_tmp_copy(buf_T *buf, char_u *tmp_copy_ffname, + int copy_type); +static int cc_compile_tmp_copy(cc_bufline_T *bufline, + char_u *tmp_copy_ffname, int copy_type); +static cc_ewline_T *cc_create_ewlist(char_u *tmp_out_ffname); +void cc_sigalrm_handler(int signum); +void cc_update_screen(void); +static void cc_free(void); +static cc_bufline_T* cc_add_buf(buf_T *buf); +static int cc_set_tmp_copy_ffname(buf_T *buf, char_u *tmp_copy_ffname); + +static int cc_started = FALSE; +static int old_p_ut = 0; + +/* + * TODO: + * + * 2. error/warning format should be added so that new languages are + * easily added to the code_check.c. + */ + +/* + * Initializes the related structures. + */ + int +cc_init(void) { + int i; + int retval; + + cc_list.cc_bufcount = 0; + cc_list.cc_list_head = NULL; + cc_list.cc_list_curr = NULL; + + retval = sem_init(&full, 0, 0); + if (retval) + return CC_FAIL; + + retval = sem_init(&empty, 0, MAX_PENDING_JOBS); + if (retval) + return CC_FAIL; + + cc_start_slave_thread(); + + for (i = 0; i < MAX_BUFLINES; ++i) + cc_bufline_ptrs[i] = NULL; + + for (i = 0; i < MAX_PENDING_JOBS; ++i) + cc_pjobs[i] = NULL; + + old_p_ut = p_ut; + p_ut = 1000; + + cc_started = TRUE; + + return CC_SUCCESS; +} + +/* + * Returns the current number of buffers in the watchlist. + */ + int +cc_get_bufcount(void) { + return cc_list.cc_bufcount; +} + +/* + * Returns TRUE if CodeCheck is already started. + */ + int +cc_get_is_started(void) { + return cc_started; +} + +/* + * CodeCheck creates a working thread which would run in the background. + * This thread is responsible of compiling the specified buffers, parsing + * the outputs and forming the corresponding error/warning lists. + */ + static int +cc_start_slave_thread(void) { + pthread_attr_t *attr = NULL; + int retval; + + attr = (pthread_attr_t *) calloc(1, sizeof(pthread_attr_t)); + + retval = pthread_attr_init(attr); + if (retval) + goto sslave_fail; + + retval = pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); + if (retval) + goto sslave_fail; + + retval = pthread_create(&cc_slave, attr, cc_slave_sroutine, NULL); + if (retval) + goto sslave_fail; + + return CC_SUCCESS; + +sslave_fail: + free(attr); + return CC_FAIL; +} + +/* + * Function updates the error/warning line numbers after new lines are inserted + * or some existing lines are removed. This is just to move the highlighted + * parts before an actual compile. + * + * TODO: This function is mainly copied from color_me.c (the preliminary + * project). So, double check if everything is OK or not!!! + */ + int +cc_update_ew_lnums(buf_T *buf, int lnum, int col, long xtra) { + if (!cc_started) + return CC_FAIL; + + cc_bufline_T *tmp_bufline; + cc_ewline_T *tmp_ewline; + char_u *line = NULL; + int dummy; + int i; + int skip = FALSE; + + tmp_bufline = cc_locate_buf_bin(buf, &dummy, &dummy, CC_FOR_FIND); + if (tmp_bufline == NULL) + return CC_FAIL; + + pthread_mutex_lock(&(tmp_bufline->buf_mutex)); + + for (tmp_ewline = tmp_bufline->buf_ewlist_head; + tmp_ewline != NULL; tmp_ewline = tmp_ewline->next) { + + skip = FALSE; + if (tmp_ewline->ew_lnum < lnum) + continue; + if (tmp_ewline->ew_lnum == lnum) { + /* is this the beginning of the line? */ + line = ml_get_buf(buf, lnum, FALSE); + for (i = col - 1; i > 0 && + (line[i] == '\t' || line[i] == ' '); --i) + ; + if (i > 0) + continue; + } + tmp_ewline->ew_lnum += xtra; + } + + pthread_mutex_unlock(&(tmp_bufline->buf_mutex)); + return CC_SUCCESS; +} + +/* + * Worker thread function, which continuously checks for unserved requests. + */ + static void * +cc_slave_sroutine(void *args) { + cc_pjob_T *tmp_pjob; + cc_bufline_T *tmp_bufline; + int retval; + int dummy; + char_u tmp_copy_ffname[MAX_PATH_LENGTH]; + char_u tmp_out_ffname[MAX_CMD_LENGTH]; + cc_ewline_T *ew_head = NULL; + + while (TRUE) { + /* consume a pending job */ + tmp_pjob = cc_pjobs_consume(); + if (tmp_pjob == NULL) + continue; + + /* when gui mode is used, an other update screen is required. */ + /* i don't know why :)!!!. */ + if (gui.in_use) + cc_update_screen(); + + /* find the buffer line */ + + /* create the temporary copy */ + retval = cc_create_tmp_copy(tmp_pjob->cc_pjob_buf, + tmp_copy_ffname, CC_STANDALONE); + if (retval == CC_FAIL) + continue; + + tmp_bufline = cc_locate_buf_bin(tmp_pjob->cc_pjob_buf, &dummy, + &dummy, CC_FOR_FIND); + if (tmp_bufline == NULL) + continue; + + /* compile the temporary copy */ + retval = cc_compile_tmp_copy(tmp_bufline, tmp_copy_ffname, + CC_STANDALONE); + if (retval == CC_FAIL) + continue; + + /* create the error list from the temporary copy */ + sprintf((char *)tmp_out_ffname, "%s.out", (char *)tmp_copy_ffname); + ew_head = cc_create_ewlist(tmp_out_ffname); + + /* having ew_head == NULL is not an error, since all + * the errors and the warnings may be cleaned. */ + + /* free the previous list & set the new list */ + pthread_mutex_lock(&(tmp_bufline->buf_mutex)); + + cc_free_ewlist(tmp_bufline->buf_ewlist_head); + tmp_bufline->buf_ewlist_head = ew_head; + + pthread_mutex_unlock(&(tmp_bufline->buf_mutex)); + + /* update the screen */ + cc_update_screen(); + + /* that would be it for this buffer */ + } +} + +/* + * Function is used to update the screen properly. + */ + void +cc_update_screen(void) { + if (!gui.in_use) { + /* in console mode updating the screen from the worker thread + * does not cause any problems. */ + update_topline(); + validate_cursor(); + update_screen(SOME_VALID); + setcursor(); + cursor_on(); + out_flush(); + } else { + /* updating the screen in gui mode is troublesome. */ + char_u bytes[3]; + + bytes[0] = CSI; + bytes[1] = KS_EXTRA; + bytes[2] = KE_REDRAW; + + add_to_input_buf(bytes, 3); + } +} + +/* + * Function returns the correct error/warning type for a specific lnum. + * Returns CC_NOEW if the lnum does not have an error or a warning. + */ + int +cc_get_ew_type(buf_T *buf, linenr_T lnum) { + if (!cc_started) + return CC_FAIL; + + cc_bufline_T *tmp_bufline; + cc_ewline_T *tmp_ewline; + int dummy; + int ew_type = CC_NOEW; + + tmp_bufline = cc_locate_buf_bin(buf, &dummy, &dummy, CC_FOR_FIND); + if (tmp_bufline == NULL) + return CC_NOEW; + + pthread_mutex_lock(&(tmp_bufline->buf_mutex)); + tmp_ewline = tmp_bufline->buf_ewlist_head; + while (tmp_ewline != NULL) { + if (tmp_ewline->ew_lnum != lnum) { + tmp_ewline = tmp_ewline->next; + continue; + } + + if (ew_type < tmp_ewline->ew_type) + ew_type = tmp_ewline->ew_type; + + tmp_ewline = tmp_ewline->next; + } + pthread_mutex_unlock(&(tmp_bufline->buf_mutex)); + return ew_type; +} + +/* + * Function creates a quickfix error list from the compiler output. + * TODO: Right now works only for .c files and the gcc compiler. + * Should be automated to work with multiple languages and multiple + * compilers. + */ + static cc_ewline_T * +cc_create_ewlist(char_u *tmp_out_ffname) { + FILE *err_file = NULL; + char_u *buf = NULL; + size_t len = 0; + size_t read; + cc_ewline_T *ew_head = NULL; + cc_ewline_T *ew_curr = NULL; + char_u *token1 = NULL; + char_u *token2 = NULL; + char_u *token3 = NULL; + char_u *token4 = NULL; + cc_ewline_T *tmp_ewline = NULL; + + + err_file = fopen((char *)tmp_out_ffname, "r"); + if (err_file == NULL) + return NULL; + + while ((read = getline((char **) &buf, &len, err_file)) != -1) { + token1 = (char_u *)strtok((char *) buf, ":"); + token2 = (char_u *)strtok(NULL, ":"); + token3 = (char_u *)strtok(NULL, ":"); + token4 = (char_u *)strtok(NULL, ":"); + + /* TODO: be sure it is gXX type of output. */ + + if (!token4) + continue; + + tmp_ewline = (cc_ewline_T *) calloc(1, sizeof(cc_ewline_T)); + STRCPY(tmp_ewline->ew_text, token4); + /* remove the last '\n' from the error/warning message */ + tmp_ewline->ew_text[STRLEN(tmp_ewline->ew_text) - 1] = '\0'; + tmp_ewline->ew_lnum = (linenr_T) atoi((char *) token2); + tmp_ewline->ew_type = token3[1] == 'w' ? CC_WARNING : CC_ERROR; + + if (!ew_head) { + ew_head = tmp_ewline; + ew_curr = tmp_ewline; + } else { + ew_curr->next = tmp_ewline; + tmp_ewline->prev = ew_curr; + ew_curr = ew_curr->next; + } + } + + /* buf should be freed, because it is allocated in getline */ + if (buf) + free(buf); + + return ew_head; +} + +/* + * Function compiles the temporary copy saving the output in a + * temporary file. + */ + static int +cc_compile_tmp_copy(cc_bufline_T *bufline, char_u *tmp_copy_ffname, + int copy_type) { + char_u cmd[MAX_CMD_LENGTH]; + + if (bufline->buf_compile_cmd[0] == NUL) + return CC_FAIL; + + /* clear the arrays. */ + memset(cmd, 0, MAX_CMD_LENGTH); + + /* direct STDOUT & STDERR to the same file, so that the output of + * this will not interfer with the vim terminal */ + sprintf((char *)cmd, "%s > %s.out 2>&1", + (char *)bufline->buf_compile_cmd, (char *)tmp_copy_ffname); +#if 1 + system((char *)cmd); +#endif + + return CC_SUCCESS; +} + +/* + * Function creates a temporary copy of the buffer. copy_type determines + * the type of the copy process. + */ + static int +cc_create_tmp_copy(buf_T *buf, char_u *tmp_copy_ffname, int copy_type) { + int retval; + char_u cmd[MAX_CMD_LENGTH]; + + switch (copy_type) { + case CC_STANDALONE: + cc_set_tmp_copy_ffname(buf, tmp_copy_ffname); + sprintf((char *)cmd, "touch %s", (char *)tmp_copy_ffname); + + system((char *)cmd); +#if 1 + retval = buf_write(buf, tmp_copy_ffname, NULL, + (linenr_T) 1, buf->b_ml.ml_line_count, NULL, + FALSE, FALSE, FALSE, TRUE); + + if (retval == FAIL) + return CC_FAIL; + else +#endif + return CC_SUCCESS; + + /* these are not implemented yet. */ + case CC_MASTER: + case CC_PROJECT: + default: + return CC_FAIL; + } +} + +/* + * Outside accessible compile request for a specific buffer. + */ + int +cc_request_compile(buf_T *buf) { + if (!cc_started) + return CC_FAIL; + + cc_pjob_T *tmp_pjob; + + tmp_pjob = (cc_pjob_T *) calloc(1, sizeof(cc_pjob_T)); + if (tmp_pjob == NULL) + return CC_FAIL; + + tmp_pjob->cc_pjob_buf = buf; + tmp_pjob->cc_pjob_type = CC_COMPILE; + + return cc_pjobs_produce(tmp_pjob); +} + +/* + * Function required to populate the pending jobs array. + * TODO: FAILURE IN pthread_mutex_unlock is SERIOUS!!! + */ + static int +cc_pjobs_produce(cc_pjob_T *tmp_pjob) { + int retval; + + sem_wait(&empty); + + retval = pthread_mutex_lock(&mutex); + if (retval) + return CC_FAIL; + + /* check if this buffer already has a pending job request. */ + /* if so directly return. */ + if (cc_pjobs_buf_exists(tmp_pjob)) { + sem_post(&empty); + retval = pthread_mutex_unlock(&mutex); + if (retval) + return CC_FAIL; + return CC_BUFEXISTS; + } + + cc_pjobs[pindex % MAX_PENDING_JOBS] = tmp_pjob; + ++pindex; + + retval = pthread_mutex_unlock(&mutex); + if (retval) + return CC_FAIL; + + retval = sem_post(&full); + if (retval) + return CC_FAIL; + + return CC_SUCCESS; +} + +/* + * Returns TRUE if there is another pending job for this process. + */ + static int +cc_pjobs_buf_exists(cc_pjob_T *tmp_pjob) { + int i; + + for (i = 0; i < MAX_PENDING_JOBS; ++i) { + if (cc_pjobs[i] == NULL || + strcmp((char *) cc_pjobs[i]->cc_pjob_buf->b_ffname, + (char *) tmp_pjob->cc_pjob_buf->b_ffname)) + continue; + else + return TRUE; + } + return FALSE; +} + +/* + * Removes the "top" element. + */ + static cc_pjob_T * +cc_pjobs_consume(void) { + int retval; + cc_pjob_T *tmp_pjob = NULL; + + tmp_pjob = (cc_pjob_T *) calloc(1, sizeof(cc_pjob_T)); + if (tmp_pjob == NULL) + return NULL; + + sem_wait(&full); + + retval = pthread_mutex_lock(&mutex); + if (retval) + goto con_fail; + + tmp_pjob->cc_pjob_type = cc_pjobs[cindex % MAX_PENDING_JOBS]->cc_pjob_type; + tmp_pjob->cc_pjob_buf = cc_pjobs[cindex % MAX_PENDING_JOBS]->cc_pjob_buf; + + /* free the old job!!! */ + free(cc_pjobs[cindex % MAX_PENDING_JOBS]); + cc_pjobs[cindex % MAX_PENDING_JOBS] = NULL; + + cindex++; + + retval = pthread_mutex_unlock(&mutex); + if (retval) + goto con_fail; + + retval = sem_post(&empty); + if (retval) + goto con_fail; + + return tmp_pjob; + +con_fail: + free(tmp_pjob); + return NULL; +} + +/* + * Checks if the specified buffer can be syntax checked or not. + */ + int +cc_is_buf_ok(buf_T *buf) { + if (!cc_started) + return CC_FAIL; + + char_u *ffname = NULL; + char_u *p = NULL; + char_u *r = NULL; + + if (buf == NULL || ((ffname = buf->b_ffname) == NULL)) + return CC_FAIL; + + /* find the file extension */ + p = (char_u *) strrchr((char *) ffname, '.'); + + if (p == NULL) + return CC_FAIL; + + /* check if this extension is supported or not */ + r = (char_u *) strstr((char *) cc_sup_exts, (char *) p); + + if (r == NULL) + return CC_FAIL; + else + return CC_SUCCESS; +} + +/* + * Returns TRUE if the specified buffer is in the watchlist. + * TODO: THIS CAN RETURN THE FOUND BUFFER TO SAVE SOME TIME. + * + */ + int +cc_is_buf_watched(buf_T *buf) { + if (!cc_started) + return CC_FAIL; + + cc_bufline_T *tmp_bufline; + int dummy; + + tmp_bufline = cc_locate_buf_bin(buf, &dummy, &dummy, CC_FOR_FIND); + if (tmp_bufline == NULL) + return FALSE; + else + return TRUE; +} + +/* + * Finds and sets a file name for the temporary copy of the buffer. + */ + static int +cc_set_tmp_copy_ffname(buf_T *buf, char_u *tmp_copy_ffname) { + char_u *buf_sfname; + char_u tmp_buf[MAX_PATH_LENGTH]; + char_u *p; + + /* clear the array. */ + memset(tmp_buf, 0, MAX_PATH_LENGTH); + + /* find the tmp_copy_ffname. */ + /* uses gettail(buf->b_sfname) because sometimes b_sfname is + * actually b_ffname. */ + buf_sfname = buf->b_sfname ? gettail(buf->b_sfname) + : gettail(buf->b_ffname); + if (buf_sfname == NULL || !strcmp((char *) buf_sfname, "")) + return CC_FAIL; + + /* finds the folder path of the buffer. */ + p = (char_u *)strstr((char *)buf->b_ffname, (char *)buf_sfname); + if (p == NULL) + return CC_FAIL; + STRNCPY(tmp_buf, buf->b_ffname, (p - buf->b_ffname)); + + /* rather than creating the copy in /tmp/, create it in the directory + * of the buffer. this solves a few issues without a lot of effort. */ + sprintf((char *) tmp_copy_ffname, "%s.cc_%s", + (char *) tmp_buf, (char *) buf_sfname); + return CC_SUCCESS; +} + +/* + * Adds the specified buffer and sets the compile command. + */ + int +cc_addbuf_setcmd(buf_T *buf, char_u *cmd) { + cc_bufline_T *tmp_bufline = NULL; + char_u tmp_compile_cmd[MAX_CMD_LENGTH]; + char_u *p = NULL; + char_u tmp_copy_ffname[MAX_PATH_LENGTH]; + size_t bname_len; + + /* clear the arrays. */ + memset(tmp_compile_cmd, 0, MAX_CMD_LENGTH); + memset(tmp_copy_ffname, 0, MAX_PATH_LENGTH); + + tmp_bufline = cc_add_buf(buf); + if (tmp_bufline == NULL) + return CC_FAIL; + + cc_set_tmp_copy_ffname(buf, tmp_copy_ffname); + + /* at this point we should update the compile command, so + * that it works for the temporary copy, rather than the + * original copy. + * *** assumes that the user entered compile command has the + * full file name of the current buffer. */ + p = (char_u *)strstr((char *)cmd, + (char *)tmp_bufline->buf_name); + if (p == NULL) + return CC_FAIL; + bname_len = STRLEN(tmp_bufline->buf_name); + + STRNCPY(tmp_compile_cmd, cmd, + (p - cmd)); + STRCAT(tmp_compile_cmd, tmp_copy_ffname); + STRCAT(tmp_compile_cmd, p + bname_len); + + STRCPY(tmp_bufline->buf_compile_cmd, tmp_compile_cmd); + + /* TODO: check if tmp_bufline->buf_compile_cmd is valid. */ + return CC_SUCCESS; +} + +/* + * Adds the specified buffer to the watch list. + * + * modified a little: returns a pointer to the inserted cc_bufline_T + * object, to make things a little faster. + * + */ + static cc_bufline_T* +cc_add_buf(buf_T *buf) { + if (!cc_started) + return NULL; + + cc_bufline_T *tmp_bufline = NULL; + cc_bufline_T *tmp_insafter = NULL; + int buf_idx; + int put_before; + int i; + int retval; + + /* check if this buffer is in the watch list, if so don't add */ + tmp_bufline = cc_locate_buf_bin(buf, &buf_idx, &put_before, CC_FOR_FIND); + if (tmp_bufline != NULL) + return NULL; + + if (cc_list.cc_bufcount == MAX_BUFLINES) + return NULL; + + tmp_bufline = (cc_bufline_T *) calloc(1, sizeof(cc_bufline_T)); + + if (tmp_bufline == NULL) + return NULL; + + tmp_bufline->buf_name = buf->b_ffname; + memset(tmp_bufline->buf_compile_cmd, 0, MAX_CMD_LENGTH); + pthread_mutex_init(&(tmp_bufline->buf_mutex), NULL); + + if (cc_list.cc_bufcount == 0) { + /* this is the first buffer */ + cc_list.cc_list_head = tmp_bufline; + tmp_bufline->prev = NULL; + tmp_bufline->next = NULL; + cc_bufline_ptrs[0] = tmp_bufline; + } else { + tmp_insafter = cc_locate_buf_bin(buf, &buf_idx, &put_before, CC_FOR_ADD); + + if (tmp_insafter == NULL) { + free(tmp_bufline); + return NULL; + } + + if (put_before) { + /* move elements in cc_bufline_ptrs */ + for (i = cc_list.cc_bufcount; i > buf_idx; --i) + cc_bufline_ptrs[i] = cc_bufline_ptrs[i - 1]; + + cc_bufline_ptrs[buf_idx] = tmp_bufline; + + tmp_bufline->prev = tmp_insafter->prev; + if (tmp_insafter->prev != NULL) + tmp_insafter->prev->next = tmp_bufline; + else + cc_list.cc_list_head = tmp_bufline; + + tmp_bufline->next = tmp_insafter; + tmp_insafter->prev = tmp_bufline; + } else { + /* move elements in cc_bufline_ptrs */ + for (i = cc_list.cc_bufcount; i > buf_idx + 1; --i) + cc_bufline_ptrs[i] = cc_bufline_ptrs[i - 1]; + + cc_bufline_ptrs[buf_idx + 1] = tmp_bufline; + + tmp_bufline->next = tmp_insafter->next; + if (tmp_bufline->next != NULL) + tmp_bufline->next->prev = tmp_bufline; + + tmp_insafter->next = tmp_bufline; + tmp_bufline->prev = tmp_insafter; + } + } + + /* create an initial compile request for this buffer */ + retval = cc_request_compile(buf); + if (retval == CC_FAIL) + return NULL; + + cc_list.cc_list_curr = tmp_bufline; + cc_list.cc_bufcount++; + + return tmp_bufline; +} + +/* + * Removes the specified buffer from the watch list. + * TODO: current implementation does not have sorted lists. + */ + int +cc_rem_buf(buf_T *buf) { + if (!cc_started) + return CC_FAIL; + + cc_bufline_T *tmp_bufline = NULL; + int buf_idx; + int put_before; + int i; + + tmp_bufline = cc_locate_buf_bin(buf, &buf_idx, &put_before, CC_FOR_REM); + + if (tmp_bufline == NULL) + return CC_NOSUCHBUF; + + if (tmp_bufline->prev != NULL) + tmp_bufline->prev->next = tmp_bufline->next; + + if (tmp_bufline->next != NULL) + tmp_bufline->next->prev = tmp_bufline->prev; + + if (tmp_bufline->buf_ewlist_head != NULL) + cc_free_ewlist(tmp_bufline->buf_ewlist_head); + + vim_free(tmp_bufline); + + /* update cc_bufline_ptrs */ + for (i = buf_idx; i < cc_list.cc_bufcount; ++i) + cc_bufline_ptrs[i] = cc_bufline_ptrs[i + 1]; + + cc_list.cc_bufcount--; + + /* update the screen in case there are some highlighted lines. */ + cc_update_screen(); + + return CC_SUCCESS; +} + +/* + * Returns a pointer to the specified buffer node. + * Returns NULL if there is no such buffer. + * This is the linear search version. + * THIS SHOULD NOT BE USED ANYMORE!!! + */ + static cc_bufline_T * +cc_locate_buf(buf_T *buf) { + cc_bufline_T *tmp_bufline = NULL; + char_u *ffname = NULL; + + ffname = buf->b_ffname; + + for (tmp_bufline = cc_list.cc_list_head; + tmp_bufline != NULL; tmp_bufline = tmp_bufline->next) { + if (strcmp((char *) tmp_bufline->buf_name, (char *) ffname)) + continue; + else + break; + } + + return tmp_bufline; +} + +/* + * Binary search of the buffer list. Has two modes: + * - search for addition (find the correct position in the list which the + * specified buffer should be inserted to)(return CC_BUFEXISTS if the + * buffer is already in the watch list), + * - search for removal (find the exact location of the buffer, return NULL + * if could not be found). + */ + static cc_bufline_T * +cc_locate_buf_bin(buf_T *buf, int *buf_idx, int *put_before, int mode) { + cc_bufline_T *tmp_bufline = NULL; + int start = 0; + int end = cc_list.cc_bufcount - 1; + int mid = (start + end) / 2; + int retval; + + while (start <= end) { + mid = (start + end) / 2; + + /* since this is a linked list we are using cc_bufline_ptrs + * to find the correct node. */ + tmp_bufline = cc_bufline_ptrs[mid]; + + retval = strcmp((char *) buf->b_ffname, (char *) tmp_bufline->buf_name); + + if (retval == 0) { + *buf_idx = mid; + if (mode == CC_FOR_ADD) + return NULL; + else if (mode == CC_FOR_REM || mode == CC_FOR_FIND) + return tmp_bufline; + } else if (retval < 0) { + end = mid - 1; + *put_before = TRUE; + } else { + start = mid + 1; + *put_before = FALSE; + } + } + + /* the buffer is not inside the list */ + *buf_idx = mid; + if (mode == CC_FOR_ADD) + return tmp_bufline; + else if (mode == CC_FOR_REM || mode == CC_FOR_FIND) + return NULL; + else /* should not be reached. */ + return NULL; +} + +/* + * Frees the specified error & warning list. + */ + static void +cc_free_ewlist(cc_ewline_T *ewlist_head) { + if (ewlist_head == NULL) + return; + + cc_ewline_T *tmp_ewline = ewlist_head; + cc_ewline_T *tmp_ewlinep = ewlist_head; + + while (tmp_ewline != NULL) { + tmp_ewline = tmp_ewline->next; + free(tmp_ewlinep); + tmp_ewlinep = tmp_ewline; + } +} + +/* + * Frees the specified buffer watch list. + * TODO: CHECK THIS BEHAVIOR!!! IMPORTANT!!! + */ + static void +cc_free_buflist(cc_info_T *cc_list_a, + cc_bufline_T *cc_bufline_ptrs_a[MAX_BUFLINES]) { + int i; + + if (cc_list_a->cc_list_head == NULL) + return; + + cc_bufline_T *tmp_bufline = cc_list_a->cc_list_head; + cc_bufline_T *tmp_buflinep = cc_list_a->cc_list_head; + + while (tmp_bufline != NULL) { + cc_free_ewlist(tmp_bufline->buf_ewlist_head); + + tmp_bufline = tmp_bufline->next; + free(tmp_buflinep); + tmp_buflinep = tmp_bufline; + } + + for (i = 0; i < cc_list_a->cc_bufcount; ++i) + cc_bufline_ptrs_a[i] = NULL; + + cc_list_a->cc_list_head = NULL; + cc_list_a->cc_list_curr = NULL; + cc_list_a->cc_bufcount = 0; +} + +/* + * Frees the memory. + */ + static void +cc_free(void) { + if (!cc_started) + return; + + cc_list.cc_list_curr = NULL; + cc_free_buflist(&cc_list, cc_bufline_ptrs); +} + +/* + * TODO: cc_exit() function. + * what else should it do??? + */ + void +cc_exit(void) { + p_ut = old_p_ut; + cc_started = FALSE; + cc_free(); +} + diff --git a/src/ex_cmds.h b/src/ex_cmds.h index e8fef0a0..77b63ad4 100644 --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -201,8 +201,12 @@ EX(CMD_cbuffer, "cbuffer", ex_cbuffer, BANG|RANGE|NOTADR|WORD1|TRLBAR), EX(CMD_cc, "cc", ex_cc, RANGE|NOTADR|COUNT|TRLBAR|BANG), +EX(CMD_ccadd, "ccadd", ex_codecheck, + EXTRA|NOTRLCOM|XFILE|MODIFY), EX(CMD_cclose, "cclose", ex_cclose, RANGE|NOTADR|COUNT|TRLBAR), +EX(CMD_ccrem, "ccrem", ex_codecheck, + NOSPC|NOTRLCOM|MODIFY), EX(CMD_cd, "cd", ex_cd, BANG|FILE1|TRLBAR|CMDWIN), EX(CMD_center, "center", ex_align, diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 84099a7c..28b256f4 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -140,6 +140,7 @@ static char_u *replace_makeprg __ARGS((exarg_T *eap, char_u *p, char_u **cmdline static char_u *repl_cmdline __ARGS((exarg_T *eap, char_u *src, int srclen, char_u *repl, char_u **cmdlinep)); static void ex_highlight __ARGS((exarg_T *eap)); static void ex_colorscheme __ARGS((exarg_T *eap)); +static void ex_codecheck __ARGS((exarg_T *eap)); static void ex_quit __ARGS((exarg_T *eap)); static void ex_cquit __ARGS((exarg_T *eap)); static void ex_quit_all __ARGS((exarg_T *eap)); @@ -6157,6 +6158,34 @@ ex_colorscheme(eap) } static void +ex_codecheck(eap) + exarg_T *eap; +{ + switch (eap->cmdidx) { + case CMD_ccadd: + if (!cc_get_is_started()) + cc_init(); + if (cc_is_buf_ok(curbuf)) { + if (*eap->arg != NUL) + cc_addbuf_setcmd(curbuf, eap->arg); + else + EMSG(_("You have to specify a valid compile command " + "in order to add this buffer to watchlist.")); + } else + EMSG(_("The present version of CodeCheck does not support " + "this language.")); + break; + case CMD_ccrem: + cc_rem_buf(curbuf); + if (cc_get_bufcount() == 0) + cc_exit(); + break; + default: + return; + } +} + + static void ex_highlight(eap) exarg_T *eap; { diff --git a/src/fileio.c b/src/fileio.c index c86f55db..6f0bd8c3 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -8473,6 +8473,12 @@ trigger_cursorhold() { int state; + /* trigger a compile request if the current buffer is in + * the watchlist and the current mode is NOT CMDLINE. */ + if ((State & CMDLINE) == 0) + if (cc_is_buf_watched(curbuf)) + cc_request_compile(curbuf); + if (!did_cursorhold && has_cursorhold() && !Recording #ifdef FEAT_INS_EXPAND && !ins_compl_active() diff --git a/src/getchar.c b/src/getchar.c index d4066c21..4b650f43 100644 --- a/src/getchar.c +++ b/src/getchar.c @@ -1618,6 +1618,20 @@ vgetc() c = K_IGNORE; } + /* Handle redraw event here, this is used by CodeCheck.*/ + if (c == K_REDRAW) { + /* gui_update_screen() does not work because of + * update_screen(0) inside it. */ + update_topline(); + validate_cursor(); + update_screen(SOME_VALID); + setcursor(); + cursor_on(); + out_flush(); + + c = K_IGNORE; + } + /* Translate K_CSI to CSI. The special key is only used to avoid * it being recognized as the start of a special key. */ if (c == K_CSI) diff --git a/src/keymap.h b/src/keymap.h index 39837e55..c61d365d 100644 --- a/src/keymap.h +++ b/src/keymap.h @@ -256,6 +256,7 @@ enum key_extra , KE_NOP /* doesn't do something */ , KE_FOCUSGAINED /* focus gained */ , KE_FOCUSLOST /* focus lost */ + , KE_REDRAW /* update the screen */ }; /* @@ -452,6 +453,8 @@ enum key_extra #define K_CURSORHOLD TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD) +#define K_REDRAW TERMCAP2KEY(KS_EXTRA, KE_REDRAW) + /* Bits for modifier mask */ /* 0x01 cannot be used, because the modifier must be 0x02 or higher */ #define MOD_MASK_SHIFT 0x02 diff --git a/src/misc1.c b/src/misc1.c index a626182a..0afd6037 100644 --- a/src/misc1.c +++ b/src/misc1.c @@ -2674,6 +2674,11 @@ changed_lines(lnum, col, lnume, xtra) #endif changed_common(lnum, col, lnume, xtra); + + /* update ew linenumbers for CodeCheck if the current buffer is + * being watched. */ + if (cc_is_buf_watched(curbuf)) + cc_update_ew_lnums(curbuf, lnum, col, xtra); } static void diff --git a/src/screen.c b/src/screen.c index 684c3942..9fb9a591 100644 --- a/src/screen.c +++ b/src/screen.c @@ -2717,6 +2717,26 @@ win_line(wp, lnum, startrow, endrow, nochange) #else extra_check = 0; #endif + + /* + * Highlight the line if this buffer is in the code_check.c watchlist, + * and there is an associated error in the corresponding error/warning + * list. + */ + if (cc_is_buf_watched(curbuf)) { + switch (cc_get_ew_type(curbuf, lnum)) { + case /*CC_WARNING*/1: + line_attr = hl_attr(HLF_WM); + break; + case /*CC_ERROR*/2: + line_attr = hl_attr(HLF_E); + break; + default: + line_attr = 0; + break; + } + } + #ifdef FEAT_SYN_HL if (syntax_present(wp->w_buffer) && !wp->w_buffer->b_syn_error) { -- 2.11.4.GIT