├── ftplugin └── d.vim ├── plugin └── dutyl.vim ├── CONTRIBUTING.md ├── autoload ├── dutyl │ ├── register.vim │ ├── configFile.vim │ ├── dub.vim │ ├── dscanner.vim │ ├── util.vim │ ├── dfmt.vim │ ├── dcd.vim │ └── core.vim └── dutyl.vim ├── README.md └── doc └── dutyl.txt /ftplugin/d.vim: -------------------------------------------------------------------------------- 1 | setlocal omnifunc=dutyl#dComplete 2 | 3 | if dutyl#register#toolExecutable('dfmt') 4 | if !get(g:, 'dutyl_dontHandleFormat') 5 | setlocal formatexpr=dutyl#formatExpressionInvoked() 6 | endif 7 | if !get(g:, 'dutyl_dontHandleIndent') 8 | setlocal indentexpr=dutyl#indentExpressionInvoked() 9 | endif 10 | endif 11 | -------------------------------------------------------------------------------- /plugin/dutyl.vim: -------------------------------------------------------------------------------- 1 | command! DUreinit call dutyl#core#instance(1) 2 | 3 | command! -nargs=1 -bang DUexecute call dutyl#runInProjectRoot(''.) 4 | 5 | command! DUConfigFileEditImportPaths call dutyl#configFile#editImportPaths() 6 | 7 | command! DUDCDstartServer call dutyl#dcd#startServer() 8 | command! DUDCDstopServer call dutyl#dcd#stopServer() 9 | command! DUDCDclearCache call dutyl#dcd#clearCache() 10 | command! DUDCDrestartServer call dutyl#dcd#stopServer() | call dutyl#dcd#startServer() 11 | 12 | command! DUddoc call dutyl#displayDDocForSymbolUnderCursor() 13 | command! -bang -nargs=? DUjump call dutyl#jumpToDeclarationOfSymbol(empty() ? 0 : ,'') 14 | command! -bang -nargs=? DUsjump call dutyl#jumpToDeclarationOfSymbol(empty() ? 0 : ,'s') 15 | command! -bang -nargs=? DUvjump call dutyl#jumpToDeclarationOfSymbol(empty() ? 0 : ,'v') 16 | 17 | command! -nargs=* -bang -complete=file DUsyntaxCheck call dutyl#syntaxCheck([],'c',1) 18 | command! -nargs=* -bang -complete=file DUlsyntaxCheck call dutyl#syntaxCheck([],'l',1) 19 | command! -nargs=* -bang -complete=file DUstyleCheck call dutyl#styleCheck([],'c',1) 20 | command! -nargs=* -bang -complete=file DUlstyleCheck call dutyl#styleCheck([],'l',1) 21 | 22 | command! -nargs=* -complete=file DUupdateCTags call dutyl#updateCTags([]) 23 | 24 | call dutyl#register#module('dub','dutyl#dub#new',0) 25 | call dutyl#register#module('dcd','dutyl#dcd#new',20) 26 | call dutyl#register#module('dfmt','dutyl#dfmt#new',30) 27 | call dutyl#register#module('dscanner','dutyl#dscanner#new',60) 28 | call dutyl#register#module('configFile','dutyl#configFile#new',100) 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | * Please keep the scope of this project in mind. Dutyl is about tool 4 | integration - if your contribution is not using D tools, and can be run on a 5 | machine that doesn't have any of the D tools installed, it's probably not a 6 | good fit for Dutyl, and you should try sending it to [the d.vim 7 | plugin](https://github.com/JesseKPhillips/d.vim) instead. For example, 8 | syntax highlighting based on Vim regular syntax highlighting facilities 9 | doesn't belong here, but syntax highlighting based on some tool that uses 10 | libdparse or something it might be a good fit. 11 | 12 | * At the beginning of `doc/dutyl.txt` there is a line that specifies the 13 | current version of Dutyl. When there are changes in the code but a new 14 | version is not yet released, there should be a `+` after the version - e.g. 15 | `Version: 1.2.3+` - to indicate that this is a different, mid-version. If 16 | that `+` is not there already, please add it as part of the PR. 17 | 18 | # Architecture outline 19 | 20 | - Dutyl is designed as a plugin framework, and it's user facing functions - 21 | like autocompletion or jump-to-definition - are implemented as plugins 22 | bundled in the release. 23 | 24 | - A plugin usually focus on a single tool - like Dub or DCD - and exposes 25 | functions that utilize that tool - like `importPaths` or `complete`. The 26 | exposed functions should be tool-agnostic - for example both Dscanner and DCD 27 | expose `declarationsOfSymbol` which accepts the same arguments and returns 28 | result in the same format. 29 | 30 | - Designed as a plugin framework 31 | 32 | - Each plugin focus on a tool(like Dub or DCD) and expose the tool's 33 | functionality as Vim functions 34 | 35 | - User-facing functions(accessible via commands) request functions from the 36 | plugins, and use them to fulfil the users' requests 37 | -------------------------------------------------------------------------------- /autoload/dutyl/register.vim: -------------------------------------------------------------------------------- 1 | function! dutyl#register#resetModules() abort 2 | let s:registeredModules={} 3 | endfunction 4 | 5 | function! dutyl#register#resetTools() abort 6 | let s:registeredTools={} 7 | endfunction 8 | 9 | function! dutyl#register#module(name,constructor,priority) abort 10 | if !exists('s:registeredModules') 11 | let s:registeredModules={} 12 | end 13 | 14 | let s:registeredModules[a:name]={'name':a:name,'constructor':a:constructor,'priority':a:priority} 15 | endfunction 16 | 17 | function! dutyl#register#tool(name,path) abort 18 | if !exists('s:registeredTools') 19 | let s:registeredTools={} 20 | end 21 | 22 | let s:registeredTools[a:name]=a:path 23 | endfunction 24 | 25 | function! dutyl#register#getToolPath(name) abort 26 | if !exists('s:registeredTools') 27 | return [a:name] 28 | end 29 | 30 | let l:result = get(s:registeredTools, a:name, a:name) 31 | if type(l:result) == type('') 32 | return [l:result] 33 | elseif type(l:result) == type([]) 34 | return l:result 35 | else 36 | throw 'Wrong type for tool '.a:name) 37 | endif 38 | endfunction 39 | 40 | function! s:sortModulesByPriority(module1,module2) abort 41 | if a:module1.priority==a:module2.priority 42 | return 0 43 | elseif a:module1.priority call s:writeStringListField(b:stringListFieldName) 74 | setlocal nomodified 75 | endfunction 76 | 77 | "Handle saving the buffer opened by s:editStringListField 78 | function! s:writeStringListField(stringListFieldName) abort 79 | let l:config=s:readConfigFile() 80 | let l:config[a:stringListFieldName]=filter(getline(1,'$'),'!empty(v:val)') 81 | call s:writeConfigFile(l:config) 82 | setlocal nomodified 83 | endfunction 84 | 85 | "Open a buffer for editing the import paths 86 | function! dutyl#configFile#editImportPaths() abort 87 | call s:editStringListField('importPaths') 88 | endfunction 89 | -------------------------------------------------------------------------------- /autoload/dutyl/dub.vim: -------------------------------------------------------------------------------- 1 | function! dutyl#dub#new() abort 2 | if empty(s:functions.projectRoot()) 3 | return {} 4 | endif 5 | if !dutyl#register#toolExecutable('dub') 6 | return {} 7 | endif 8 | 9 | let l:result={} 10 | let l:result=extend(l:result,s:functions) 11 | return l:result 12 | endfunction 13 | 14 | let s:functions={} 15 | 16 | let s:DEFINING_FILES = ['dub.sdl', 'dub.json', 'package.json', 'dub.selections.json'] 17 | 18 | function! s:functions.projectRoot() abort 19 | let l:dubFileMatches=dutyl#util#globInParentDirectories(s:DEFINING_FILES) 20 | if empty(l:dubFileMatches) 21 | return '' 22 | else 23 | return fnamemodify(l:dubFileMatches[0],':h') 24 | endif 25 | endfunction 26 | 27 | "Return all the import paths DCD knows about, plus the ones in 28 | "g:dutyl_stdImportPaths 29 | function! s:functions.importPaths() dict abort 30 | let l:result=exists('g:dutyl_stdImportPaths') ? copy(g:dutyl_stdImportPaths) : [] 31 | 32 | let l:definingFiles = dutyl#util#globInParentDirectories(s:DEFINING_FILES) 33 | let l:definingFilesModificationTime = {} 34 | for l:file in l:definingFiles 35 | let l:definingFilesModificationTime[l:file] = getftime(l:file) 36 | endfor 37 | 38 | if has_key(self.cache.dub, 'definingFilesModificationTime') 39 | if self.cache.dub.definingFilesModificationTime == l:definingFilesModificationTime 40 | return self.cache.dub.importPaths 41 | endif 42 | endif 43 | 44 | try 45 | let l:info=s:dubDescribe() 46 | for l:package in l:info.packages 47 | for l:importPath in l:package.importPaths 48 | if dutyl#util#isPathAbsolute(l:importPath) 49 | call add(l:result,l:importPath) 50 | else 51 | let l:absoluteImportPath=globpath(l:package.path,l:importPath,1) 52 | if !empty(l:absoluteImportPath) 53 | call add(l:result,l:absoluteImportPath) 54 | endif 55 | endif 56 | endfor 57 | endfor 58 | catch /.*/ 59 | echo "Can not get dub project import path!" 60 | endtry 61 | 62 | let self.cache.dub.importPaths = dutyl#util#normalizePaths(l:result) 63 | let self.cache.dub.definingFilesModificationTime = l:definingFilesModificationTime 64 | return self.cache.dub.importPaths 65 | endfunction 66 | 67 | "Calls 'dub describe' and turns the result to Vim's data types 68 | function! s:dubDescribe() abort 69 | let l:result=dutyl#util#runInDirectory(s:functions.projectRoot(), 70 | \function('dutyl#core#runTool'),'dub',['describe','--annotate', '--vquiet']) 71 | if !empty(dutyl#core#shellReturnCode()) 72 | throw 'Failed to execute `dub describe`' 73 | endif 74 | 75 | "If package.json instead of dub.json or visa versa, dub will sometimes 76 | "complain but will still print the output. We want to remove that warning: 77 | let l:result=substitute(l:result,'\v(^|\n|\r)There was no.{-}($|\n|\r)','','g') 78 | 79 | "Replace true with 1 and false with 0 80 | let l:result=substitute(l:result,'\vtrue(\,?)[\n\r]','1\1\n','g') 81 | let l:result=substitute(l:result,'\vfalse(\,?)[\n\r]','0\1\n','g') 82 | 83 | "Remove linefeeds 84 | let l:result=substitute(l:result,'\v[\n\r]',' ','g') 85 | return eval(l:result) 86 | endfunction 87 | -------------------------------------------------------------------------------- /autoload/dutyl/dscanner.vim: -------------------------------------------------------------------------------- 1 | function! dutyl#dscanner#new() abort 2 | if !dutyl#register#toolExecutable('dscanner') 3 | return {} 4 | endif 5 | 6 | let l:result={} 7 | let l:result=extend(l:result,s:functions) 8 | return l:result 9 | endfunction 10 | 11 | let s:functions={} 12 | 13 | 14 | "Retrieve declaration location using Dscanner 15 | function! s:functions.declarationsOfSymbol(args) abort 16 | "Run Dscanner 17 | let l:scanResult=dutyl#core#runTool('dscanner',['-d',a:args.symbol]) 18 | 19 | "Try looking at the import paths. Don't do it if we can find the symbol 20 | "here - scanning all the import paths takes a very long time! 21 | if 0!=dutyl#core#shellReturnCode() || empty(l:scanResult) 22 | let l:scanResult=dutyl#core#runTool('dscanner',['-d',a:args.symbol]+a:args.importPaths) 23 | endif 24 | 25 | if 0!=dutyl#core#shellReturnCode() 26 | return [] 27 | endif 28 | 29 | let l:result=[] 30 | for l:resultLine in dutyl#util#splitLines(l:scanResult) 31 | let l:parsedLine=matchlist(l:resultLine,'\v^(.*)\((\d+)\:(\d+)\)$') 32 | if 3') 57 | let l:signatures = l:dutyl.signaturesForSymobolInBuffer(l:args) 58 | let l:ddocs = l:dutyl.ddocForSymobolInBuffer(l:args) 59 | 60 | for l:sig in l:signatures 61 | echo "\t" l:sig 62 | endfor 63 | 64 | if !empty(l:signatures) && !empty(l:ddocs) 65 | echo ' ' 66 | endif 67 | 68 | for l:i in range(len(l:ddocs)) 69 | if 0 < l:i 70 | "Print a vertical line: 71 | echo repeat('_', &columns - 1) 72 | echo ' ' 73 | endif 74 | echo l:ddocs[l:i] 75 | endfor 76 | endfunction 77 | 78 | "If symbol is a string - jump to the declaration of that string. If symbol is 79 | "an empty non-string - jump to the symbol under the cursor. If symbol is 80 | "non-empty non-string - jump to the symbol under the cursor using text 81 | "search(ignoring it's context). 82 | "Set splitType to '', 's' or 'v' to determine if and how the window will split 83 | "before jumping. 84 | function! dutyl#jumpToDeclarationOfSymbol(symbol,splitType) abort 85 | try 86 | if type(a:symbol)==type('') 87 | let l:dutyl=dutyl#core#requireFunctions('importPaths','declarationsOfSymbol') 88 | let l:args=dutyl#core#gatherCommonArguments(l:dutyl) 89 | let l:args.symbol=a:symbol 90 | let l:declarationLocations=l:dutyl.declarationsOfSymbol(l:args) 91 | else 92 | if empty(a:symbol) 93 | let l:dutyl=dutyl#core#requireFunctions('importPaths','declarationsOfSymbolInBuffer') 94 | let l:args=dutyl#core#gatherCommonArguments(l:dutyl) 95 | let l:args.symbol=expand('') 96 | let l:declarationLocations=l:dutyl.declarationsOfSymbolInBuffer(l:args) 97 | else 98 | let l:dutyl=dutyl#core#requireFunctions('importPaths','declarationsOfSymbol') 99 | let l:args=dutyl#core#gatherCommonArguments(l:dutyl) 100 | let l:args.symbol=expand('') 101 | let l:declarationLocations=l:dutyl.declarationsOfSymbol(l:args) 102 | endif 103 | endif 104 | catch 105 | echoerr 'Unable to find declaration: '.v:exception 106 | return 107 | endtry 108 | 109 | if empty(l:declarationLocations) 110 | echo 'Unable to find declaration for symbol `'.l:args.symbol.'`' 111 | elseif 1==len(l:declarationLocations) 112 | call dutyl#util#splitWindowBasedOnArgument(a:splitType) 113 | call dutyl#core#jumpToPositionPushToTagStack(l:declarationLocations[0]) 114 | else 115 | let l:selectedLocationIndex=dutyl#util#inputList('Multiple declarations found:', 116 | \map(copy(l:declarationLocations),'printf("%s(%s:%s)",get(v:val,"file","current file"),v:val.line,v:val.column)'), 117 | \'*MORE*') 118 | if 0<=l:selectedLocationIndex 119 | call dutyl#util#splitWindowBasedOnArgument(a:splitType) 120 | call dutyl#core#jumpToPositionPushToTagStack(l:declarationLocations[l:selectedLocationIndex]) 121 | endif 122 | endif 123 | endfunction 124 | 125 | "Runs a syntax check and sets the quickfix or the location list to it's 126 | "results. Arguments: 127 | " - files: a list of files to include in the syntax check. Send an empty list 128 | " to let the tool decide which files to take 129 | " - targetList: 'c'/'q' for the quickfix list, 'l' for the location list 130 | " - jump: nonzero value to automatically jump to the first entry 131 | function! dutyl#syntaxCheck(files,targetList,jump) 132 | try 133 | let l:dutyl=dutyl#core#requireFunctions('syntaxCheck') 134 | catch 135 | echoerr 'Unable to check syntax: '.v:exception 136 | return 137 | endtry 138 | let l:args=dutyl#core#gatherCommonArguments(l:dutyl) 139 | let l:args.files=a:files 140 | let l:checkResult=l:dutyl.syntaxCheck(l:args) 141 | call dutyl#util#setQuickfixOrLocationList(l:checkResult,a:targetList,a:jump) 142 | endfunction 143 | 144 | "Runs a style check and sets the quickfix or the location list to it's 145 | "results. Arguments: 146 | " - files: a list of files to include in the style check. Send an empty list 147 | " to let the tool decide which files to take 148 | " - targetList: 'c'/'q' for the quickfix list, 'l' for the location list 149 | " - jump: nonzero value to automatically jump to the first entry 150 | function! dutyl#styleCheck(files,targetList,jump) 151 | try 152 | let l:dutyl=dutyl#core#requireFunctions('styleCheck') 153 | catch 154 | echoerr 'Unable to check style: '.v:exception 155 | return 156 | endtry 157 | let l:args=dutyl#core#gatherCommonArguments(l:dutyl) 158 | let l:args.files=a:files 159 | let l:checkResult=l:dutyl.styleCheck(l:args) 160 | call dutyl#util#setQuickfixOrLocationList(l:checkResult,a:targetList,a:jump) 161 | endfunction 162 | 163 | 164 | "Update the CTags file. 165 | function! dutyl#updateCTags(paths) abort 166 | try 167 | let l:dutyl=dutyl#core#requireFunctions('generateCTags') 168 | catch 169 | echoerr 'Unable to update CTags: '.v:exception 170 | return 171 | endtry 172 | let l:args=dutyl#core#gatherCommonArguments(l:dutyl) 173 | if !empty(a:paths) 174 | let l:args.files=a:paths 175 | endif 176 | let l:tagList=l:dutyl.generateCTags(l:args) 177 | let l:tagsFile='tags' 178 | if exists('g:dutyl_tagsFileName') 179 | let l:tagsFile=g:dutyl_tagsFileName 180 | endif 181 | call writefile(l:tagList,l:tagsFile) 182 | endfunction 183 | 184 | "Return the project's root 185 | function! dutyl#projectRoot() abort 186 | try 187 | let l:dutyl=dutyl#core#requireFunctions('projectRoot') 188 | catch 189 | echoerr 'Unable to find project root: '.v:exception 190 | return 191 | endtry 192 | return l:dutyl.projectRoot() 193 | endfunction 194 | 195 | "Runs a command in the project's root 196 | function! dutyl#runInProjectRoot(command) abort 197 | try 198 | let l:dutyl=dutyl#core#requireFunctions('projectRoot') 199 | catch 200 | echoerr 'Unable to find project root: '.v:exception 201 | return 202 | endtry 203 | call dutyl#util#runInDirectory(l:dutyl.projectRoot(),a:command) 204 | endfunction 205 | 206 | "Assign this to 'formatexpr' 207 | function! dutyl#formatExpressionInvoked() abort 208 | try 209 | let l:dutyl=dutyl#core#requireFunctions('formatCode') 210 | catch 211 | echoerr 'Unable to format code: '.v:exception 212 | return 213 | endtry 214 | let l:origLines = getline(v:lnum, v:lnum + v:count - 1) 215 | let l:formattedLines = l:dutyl.formatCode(l:origLines) 216 | 217 | "Add/remove lines from the buffer to fit to the formatted lines 218 | if v:count > len(l:formattedLines) 219 | execute (v:lnum + len(l:formattedLines)).','.(v:lnum + v:count - 1) 'delete' 220 | elseif v:count < len(l:formattedLines) 221 | call append(v:lnum + v:count - 1, repeat([''], len(l:formattedLines) - v:count)) 222 | endif 223 | 224 | call setline(v:lnum, l:formattedLines) 225 | endfunction 226 | 227 | function! dutyl#indentExpressionInvoked() abort 228 | try 229 | let l:dutyl=dutyl#core#requireFunctions('calcIndentForLastLineOfCode') 230 | catch 231 | echoerr 'Unable to indent code: '.v:exception 232 | return 233 | endtry 234 | return l:dutyl.calcIndentForLastLineOfCode(getline(1, '.')) 235 | endfunction 236 | -------------------------------------------------------------------------------- /doc/dutyl.txt: -------------------------------------------------------------------------------- 1 | *dutyl.txt* 2 | 3 | 4 | Author: Idan Arye 5 | License: Same terms as Vim itself (see |license|) 6 | 7 | Version: 1.6.0+ 8 | 9 | INTRODUCTION *dutyl* 10 | 11 | Dutyl operates various Dlang tools to help you program D in Vim. Instead of 12 | having a separate plugin for each tool, Dutyl can use multiple plugins and 13 | use them together - for example, use DUB to get a list of import paths the 14 | project is using and pass that list to DCD to get autocompleting for symbols 15 | that come from libraries. Dutyl has a module(/plugin) system that allows tools 16 | to back up each other - so for example if a project doesn't use DUB, Dutyl can 17 | back up reading the import paths from a static configuration file. 18 | 19 | Currently supported features: 20 | 21 | * Getting the imports list from DUB or from a configuration file 22 | * Autocompletion using DCD 23 | * Finding DDoc using DCD 24 | * Finding declarations using DCD or Dscanner 25 | * Syntax and style checks using Dscanner 26 | * Updating the tags file using Dscanner 27 | * Recognizing the project's root and running commands there 28 | * Formatting code using dfmt 29 | * Indenting using dfmt 30 | 31 | 32 | REQUIREMENTS *dutyl-requirements* 33 | 34 | Dutyl requires the tools that it uses. If you want it to use DUB to get info 35 | about the project, you need DUB(http://code.dlang.org/download). If you want 36 | it to use DCD for autocompletion, you need 37 | DCD(https://github.com/Hackerpilot/DCD)(currently tested with version 0.4.0). 38 | If you want it to use Dscanner, you need 39 | Dscanner(https://github.com/Hackerpilot/Dscanner). If you want it to use 40 | dfmt, you need dfmt(https://github.com/Hackerpilot/dfmt). 41 | 42 | 43 | CONFIGURATION *dutyl-configuration* 44 | 45 | Use *g:dutyl_stdImportPaths* to specify the standard library import paths. 46 | Example: > 47 | let g:dutyl_stdImportPaths=['/usr/include/dlang/dmd'] 48 | < 49 | You must either set g:dutyl_stdImportPaths or configure these paths in DCD 50 | itself, or else DCD won't be able to recognize standard library symbols. 51 | 52 | If you want to never add the closing paren in calltips completions, set 53 | *g:dutyl_neverAddClosingParen* to 1: > 54 | let g:dutyl_neverAddClosingParen = 1 55 | < 56 | Dutyl will assume that tools are in the system's PATH. If they are not, you'll 57 | have to supply the path for them using *dutyl#register#tool* like so: > 58 | call dutyl#register#tool('dcd-client', '/path/to/DCD/dcd-client') 59 | call dutyl#register#tool('dcd-server', '/path/to/DCD/dcd-server') 60 | < 61 | Note: If you are using a plugin manager(like Pathogen or Vundle), make sure 62 | that you only call |dutyl#register#tool| after you run the plugin manager's 63 | command for updating the runtime path(pathogen#infect in case of Pathogen, 64 | vundle#end in case of Vundle, or whatever the command is for whatever the tool 65 | you are using). 66 | 67 | If you want to use some complex command for running a tool - for example, if 68 | you manage your tools with Dub - you will have to structure the tool path as 69 | an array: > 70 | call dutyl#register#tool('dfmt', ['dub', '--quiet', 'run', '--build=release', 'dfmt', '--']) 71 | < 72 | Note that if you use this method, Dutyl will not be able to verify that the 73 | tool is present(only that Dub is present), and that you need to use 74 | "--quiet"(or anything similar) to make sure there is no extra output that may 75 | confuse Dutyl. 76 | 77 | Under Windows, Dutyl uses VimProc(https://github.com/Shougo/vimproc.vim) when 78 | available to prevent opening a console windows every time a command needs to 79 | be ran. To prevent using VimProc, set *g:dutyl_dontUseVimProc* to 1: > 80 | let g:dutyl_dontUseVimProc = 1 81 | < 82 | 83 | Dutyl will use a local file named "tags" for tags. If you want to everride 84 | this, set *g:dutyl_tagsFileName* to the name of the new tags file: > 85 | let g:dutyl_tagsFileName='newnamefortagsfile' 86 | < 87 | Note that the new tags file name will still have to be in |'tags'| in order 88 | for Vim to recognize it. 89 | 90 | If dfmt is detected in your path(or if you set it's path via 91 | *dutyl#register#tool*), 92 | Dutyl will automatically set 'formatexpr' and 'indentexpr' when you enter a D 93 | file. To disable this, set *g:dutyl_dontHandleFormat* and/or 94 | *g:dutyl_dontHandleIndent* to 1: > 95 | let g:dutyl_dontHandleFormat = 1 96 | let g:dutyl_dontHandleIndent = 1 97 | < 98 | 99 | 100 | DCD SERVER *dutyl-dcd-server* 101 | 102 | Dutyl can not use DCD if the DCD server is not running. To start the DCD 103 | server, use *:DUDCDstartServer*. To stop it, use *:DUDCDstopServer*. To 104 | restart it, use *:DUDCDrestartServer*. To clear the cache, use 105 | *:DUDCDclearCache*. 106 | 107 | 108 | CONFIG FILE *dutyl-config-file* 109 | 110 | The config file contains information about the project, and is useful when 111 | that information can not be gathered automatically from DUB(usually because 112 | it's not a DUB project). 113 | 114 | The config file is named ".dutyl.configFile" and it is a serialization of a 115 | Vim |Dictionary|. 116 | 117 | The config file can contain a list of import paths for the project. The source 118 | code of the project must be one of them - Dutyl won't guess for you whether or 119 | not you are using a dedicated folder for the source files and what's it's 120 | name. To edit that list, run |:DUConfigFileEditImportPaths|. It'll open buffer 121 | where you write each import path in a separate line and when you save that 122 | buffer it'll be written to the config file. 123 | 124 | 125 | DUB *dutyl-dub* 126 | 127 | If the project's folder has "dub.json" or "package.json", Dutyl will use DUB 128 | to automatically figure out the import paths. 129 | 130 | Note: If the project file is badly formatted(rejected by DUB), or if contains 131 | dependencies that have not yet been downloaded by DUB, Dutyl's call to DUB 132 | will fail and it won't be able to get the import paths. 133 | 134 | 135 | PROJECT ROOT *dutyl-project-root* 136 | 137 | Dutyl knows about the project's root - either from DUB or from the config file 138 | location. You can get the project's root with the *dutyl#projectRoot()* 139 | function. You can run any Vim command in the project's root with *:DUexecute*. 140 | :DUexecute! can be used to run shell commands(sugar for "DUexecute !"). 141 | 142 | 143 | AUTOCOMPLETE *dutyl-autocomplete* 144 | 145 | Autocomplete requires DCD(see |dutyl-dcd-server|) and either DUB or a config 146 | file. 147 | 148 | The autocomplete omnifunc is set automatically when you edit a D source file. 149 | 150 | 151 | DDOC-PRINTING *dutyl-ddoc-printing* 152 | 153 | DDoc printing requires DCD(see |dutyl-dcd-server|) and either DUB or a config 154 | file. 155 | 156 | Place the cursor on a symbol and run *:DUddoc* to print all DDocs associated 157 | with that symbol. 158 | 159 | 160 | JUMP-TO-DECLARATION *dutyl-jump-to-declaration* 161 | 162 | Jumping to declaration either DCD(see |dutyl-dcd-server|) or Dscanner and 163 | either DUB or a config file. 164 | 165 | Place the cursor on a symbol and run *:DUjump* to jump to the declaration of 166 | that symbol. 167 | 168 | You can also send the symbol you are looking for as an argument to :DUjump. 169 | 170 | Using bang(:DUjump!) will force Dutyl to ignore the context and jump by text 171 | only. This will yield less accurately filtered results, but is useful when DCD 172 | can't track the declaration from the place where it is used. 173 | 174 | *:DUsjump* and *:DUvjump* split the window horizontally or vertically before 175 | they jump. They will not split the window when Dutyl can't find the symbol or 176 | when the user canceled the search. 177 | 178 | 179 | SYNTAX-AND-STYLE-CHECKS *dutyl-style-check* 180 | 181 | Syntax and style checks requires Dscanner. 182 | 183 | Use *:DUsyntaxCheck* to run a syntax check and store the results in the 184 | quickfix list. 185 | 186 | Use *:DUlsyntaxCheck* to run a syntax check and store the results in the 187 | location list. 188 | 189 | Use *:DUstyleCheck* to run a style check and store the results in the quickfix 190 | list. 191 | 192 | Use *:DUlstyleCheck* to run a style check and store the results in the 193 | location list. 194 | 195 | All these commands accept as arguments file names to perform the check on. If 196 | no file name is given, the tool will decide which files to check. In case of 197 | Dscanner(currently the only tool that does these checks) it'll use all the D 198 | files in the working directory and it's sub-directories. 199 | 200 | All these commands will jump to the first syntax/style error/warning - unless 201 | invoked with a bang(eg. :DUsyntaxCheck!). 202 | 203 | 204 | UPDATE-CTAGS *dutyl-update-ctags* 205 | 206 | CTags updating checks requires Dscanner. 207 | 208 | Use *:DUupdateCTags* to update the CTags. The tags will be written to a local 209 | file named "tags", unless |g:dutyl_tagsFileName| is set. 210 | 211 | 212 | FORMATTING *dutyl-formatting* 213 | 214 | Formatting is done using dfmt and invoked automatically or by using Vim's |gq| 215 | operator. dfmt read it's option using .editorconfig files, so you can't and 216 | don't need to configure the style from Dutyl. 217 | 218 | INDENTATION *dutyl-indentation* 219 | 220 | Indentation is done using dfmt and invoked automatically or by using Vim's |=| 221 | operator. Note that Vim's indent works by evaluating 'indentexpr' for each 222 | line, so indenting many lines at once can be slow since it needs to invoke 223 | dfmt many times. 224 | -------------------------------------------------------------------------------- /autoload/dutyl/core.vim: -------------------------------------------------------------------------------- 1 | "Create and return the instance object. If there already is an instance object 2 | "don't re-create it - unless the argument reinit is not-empty 3 | function! dutyl#core#instance(reinit) abort 4 | if !exists('s:instance') || !empty(a:reinit) 5 | let s:instance=dutyl#core#create() 6 | endif 7 | return s:instance 8 | endfunction 9 | 10 | function! dutyl#core#requireFunctions(...) abort 11 | return call(dutyl#core#instance(0).requireFunctions,a:000,s:instance) 12 | endfunction 13 | 14 | "Create the base Dutyl object and load all modules to it 15 | function! dutyl#core#create() abort 16 | let l:result = {} 17 | 18 | let l:modules = [] 19 | let l:cache = {} 20 | for l:moduleDefinition in dutyl#register#list() 21 | let l:module = function(l:moduleDefinition.constructor)() 22 | if !empty(l:module) "Empty module = can not be used 23 | let l:module.name = l:moduleDefinition.name 24 | if !has_key(l:module, 'checkFunction') 25 | let l:module.checkFunction = function('s:checkFunction') 26 | endif 27 | call add(l:modules, l:module) 28 | let l:cache[l:module.name] = {} 29 | endif 30 | endfor 31 | 32 | let l:result.modules = l:modules 33 | let l:result.cache = l:cache 34 | let l:result.requireFunctions = function('s:requireFunctions') 35 | 36 | return l:result 37 | endfunction 38 | 39 | "Base function for testing if a module supports a function 40 | function! s:checkFunction(functionName) dict abort 41 | return !has_key(self,a:functionName) 42 | endfunction 43 | 44 | "Creates an object with the required functions from the modules, based on 45 | "module priority 46 | function! s:requireFunctions(...) dict abort 47 | let l:result = {'obj': self, 'cache': self.cache} 48 | for l:functionName in a:000 49 | if exists('l:Function') 50 | unlet l:Function 51 | endif 52 | let l:reasons = [] 53 | for l:module in self.modules 54 | unlet! l:reason 55 | let l:reason = l:module.checkFunction(l:functionName) 56 | if empty(l:reason) 57 | let l:Function = l:module[l:functionName] 58 | break 59 | elseif type('')==type(l:reason) 60 | let l:reasons = add(l:reasons,l:reason) 61 | endif 62 | endfor 63 | if exists('l:Function') 64 | let l:result[l:functionName] = l:Function 65 | else 66 | let l:errorMessage = 'Function `' . l:functionName . '` is not supported by currently loaded Dutyl modules.' 67 | if !empty(l:reasons) 68 | let l:errorMessage = l:errorMessage.' Possible reasons: ' . join(l:reasons, ', ') 69 | endif 70 | throw l:errorMessage 71 | endif 72 | endfor 73 | return l:result 74 | endfunction 75 | 76 | "Return 1 if it's OK to use VimProc 77 | function! s:useVimProcInWindows() 78 | if !exists(':VimProcBang') 79 | return 0 80 | elseif !exists('g:dutyl_dontUseVimProc') 81 | return 1 82 | else 83 | return empty(g:dutyl_dontUseVimProc) 84 | endif 85 | endfunction 86 | 87 | "Use vimproc if available under windows for escaping characters - because 88 | "that's what the dutyl#core#system command will use! 89 | function! dutyl#core#shellescape(string) abort 90 | if has('win32') && s:useVimProcInWindows() "We don't need vimproc when we use linux 91 | return vimproc#shellescape(a:string) 92 | else 93 | return shellescape(a:string) 94 | endif 95 | endfunction 96 | 97 | "Use vimproc if available under windows to prevent opening a console window 98 | function! dutyl#core#system(command,...) abort 99 | if has('win32') && s:useVimProcInWindows() "We don't need vimproc when we use linux 100 | if empty(a:000) 101 | return vimproc#system(a:command) 102 | else 103 | if type(a:000[0]) == type([]) 104 | return vimproc#system(a:command, join(a:000[0], "\n")) 105 | else 106 | return vimproc#system(a:command, a:000[0]) 107 | endif 108 | endif 109 | else 110 | if empty(a:000) 111 | return system(a:command) 112 | else 113 | return system(a:command,a:000[0]) 114 | endif 115 | endif 116 | endfunction 117 | 118 | "Returns the return code from the last run of dutyl#core#system 119 | function! dutyl#core#shellReturnCode() abort 120 | if has('win32') && s:useVimProcInWindows() "We don't need vimproc when we use linux 121 | return vimproc#get_last_status() 122 | else 123 | return v:shell_error 124 | endif 125 | endfunction 126 | 127 | "Create the command line for running a tool 128 | function! s:createRunToolCommand(tool,args) abort 129 | let l:tool = dutyl#register#getToolPath(a:tool) 130 | if !executable(l:tool[0]) 131 | throw '`'.l:tool[0].'` is not executable' 132 | endif 133 | let l:result=join(map(copy(l:tool), 'dutyl#core#shellescape(v:val)'), ' ') 134 | if type('')==type(a:args) 135 | let l:result=l:result.' '.a:args 136 | elseif type([])==type(a:args) 137 | for l:arg in a:args 138 | let l:result=l:result.' '.dutyl#core#shellescape(l:arg) 139 | endfor 140 | endif 141 | return l:result 142 | endfunction 143 | 144 | "Like s:createRunToolCommand, but doesn't try to use VimProc even if Dutyl is 145 | "configured to use it. This is used when we want to run things in the 146 | "background. 147 | function! s:createRunToolCommandIgnoreVimproc(tool,args) abort 148 | let l:tool=dutyl#register#getToolPath(a:tool) 149 | if !executable(l:tool[0]) 150 | throw '`'.l:tool[0].'` is not executable' 151 | endif 152 | let l:result=join(map(copy(l:tool), 'shellescape(v:val)'), ' ') 153 | if type('')==type(a:args) 154 | let l:result=l:result.' '.a:args 155 | elseif type([])==type(a:args) 156 | for l:arg in a:args 157 | let l:result=l:result.' '.shellescape(l:arg) 158 | endfor 159 | endif 160 | return l:result 161 | endfunction 162 | 163 | "Run a tool and return the result 164 | function! dutyl#core#runTool(tool,args,...) abort 165 | return call(function('dutyl#core#system'),[s:createRunToolCommand(a:tool,a:args)]+a:000) 166 | endfunction 167 | 168 | function! dutyl#core#runToolIgnoreStderr(tool,args,...) abort 169 | if has('win32') 170 | let l:ignoreStderrSuffix = ' 2> nul' 171 | else 172 | let l:ignoreStderrSuffix = ' 2> /dev/null' 173 | endif 174 | return call(function('dutyl#core#system'),[s:createRunToolCommand(a:tool,a:args).l:ignoreStderrSuffix]+a:000) 175 | endfunction 176 | 177 | "Check if a tool is executable. If not - it can not be used 178 | function! dutyl#core#toolExecutable(tool) abort 179 | return dutyl#register#toolExecutable(a:tool) 180 | endfunction 181 | 182 | "Run a tool in the background 183 | function! dutyl#core#runToolInBackground(tool,args) abort 184 | if has('win32') 185 | silent execute '!start '.s:createRunToolCommandIgnoreVimproc(a:tool,a:args) 186 | else 187 | silent execute '!nohup '.s:createRunToolCommand(a:tool,a:args).' > /dev/null 2>&1 &' 188 | endif 189 | endfunction 190 | 191 | "Return the byte position. The arguments are the line and the column: 192 | " - Use current line if line argument not supplied. Can be string 193 | " - Use current column if column argument not supplied. Must be numeric 194 | " Always uses unix file format. 195 | function! dutyl#core#bytePosition(...) abort 196 | let l:line=get(a:000,0,'.') 197 | let l:column=get(a:000,1,col('.')) 198 | 199 | let l:oldFileFormat=&fileformat 200 | try 201 | set fileformat=unix 202 | return line2byte(l:line)+l:column-1 203 | finally 204 | let &fileformat=l:oldFileFormat 205 | endtry 206 | endfunction 207 | 208 | "Convert byte position in the current buffer to row and column. 209 | " Always uses unix file format. 210 | function! dutyl#core#bytePosition2rowAndColumnCurrentBuffer(bytePos) abort 211 | let l:oldFileFormat=&fileformat 212 | try 213 | set fileformat=unix 214 | let l:line=byte2line(a:bytePos) 215 | let l:lineStart=line2byte(l:line) 216 | let l:column=a:bytePos-l:lineStart+2 217 | return {'bytePos':a:bytePos,'line':l:line,'column':l:column} 218 | finally 219 | let &fileformat=l:oldFileFormat 220 | endtry 221 | endfunction 222 | 223 | "Convert byte position from another file to row and column. 224 | function! dutyl#core#bytePosition2rowAndColumnAnotherFile(fileName,bytePos) abort 225 | let l:column=a:bytePos 226 | let l:lineNumber=1 227 | for l:line in readfile(a:fileName,1) 228 | let l:lineLength=strlen(l:line) 229 | if l:column <= l:lineLength 230 | return { 231 | \'file':a:fileName, 232 | \'bytePos':a:bytePos, 233 | \'line':l:lineNumber, 234 | \'column':l:column+1 235 | \} 236 | endif 237 | let l:lineNumber+=1 238 | let l:column-=l:lineLength+1 "The +1 is for the linefeed character! 239 | endfor 240 | throw 'Byte position '.a:bytePos.' is larger than file '.a:fileName 241 | endfunction 242 | 243 | "Jump to a position supplied in the arguments. Expected keys of args: 244 | " - file: Leave false for current buffer 245 | " - line, column: Exactly what it says on the tin 246 | " - bytePos: Only used if line is not supplied 247 | function! dutyl#core#jumpToPosition(args) abort 248 | if has_key(a:args,'file') 249 | let l:bufnr=bufnr(a:args.file) 250 | if 0<=l:bufnr 251 | execute 'buffer '.l:bufnr 252 | else 253 | execute 'edit '.a:args.file 254 | endif 255 | endif 256 | if has_key(a:args,'line') 257 | execute ':'.a:args.line 258 | if has_key(a:args,'column') 259 | execute 'normal! '.strdisplaywidth(getline('.')[0:a:args.column-1]).'|' 260 | endif 261 | elseif 262 | "We'd rather not use this option - it has some problems with tabs... 263 | execute 'goto '.a:args.bytePos 264 | endif 265 | endfunction 266 | 267 | "Like dutyl#core#jumpToPosition, but also pushes to the tag stack 268 | function! dutyl#core#jumpToPositionPushToTagStack(args) abort 269 | "based on http://vim.1045645.n5.nabble.com/Modifying-the-tag-stack-tp1158229p1158240.html 270 | let l:tmpTagsFile=tempname() 271 | let l:tagName='dutyl_tag_'.localtime() 272 | let l:file=fnamemodify(get(a:args,'file',expand('%')),':p') 273 | let l:oldTags=&tags 274 | try 275 | call writefile([l:tagName."\t".l:file."\t0"],l:tmpTagsFile) 276 | let &tags=l:tmpTagsFile 277 | execute 'tag '.l:tagName 278 | call dutyl#core#jumpToPosition(a:args) 279 | finally 280 | let &tags=l:oldTags 281 | call delete(l:tmpTagsFile) 282 | endtry 283 | endfunction 284 | 285 | "Gather the arguments commonly used by the various operations. Extract as many 286 | "common arguments as possible from the supplied dutyl object. 287 | function! dutyl#core#gatherCommonArguments(dutyl) abort 288 | let l:result={ 289 | \'bufferLines':getline(1,'$'), 290 | \'bytePos':dutyl#core#bytePosition(), 291 | \'lineNumber':line('.'), 292 | \'columnNumber':col('.'), 293 | \} 294 | if has_key(a:dutyl,'importPaths') 295 | let l:result['importPaths']=a:dutyl.importPaths() 296 | endif 297 | 298 | return l:result 299 | endfunction 300 | --------------------------------------------------------------------------------