├── .gitignore ├── .python-version ├── Comments.tmPreferences ├── Default (Linux).sublime-keymap ├── LICENSE ├── Nextflow.sublime-commands ├── Nextflow.sublime-settings ├── Nextflow.sublime-syntax ├── README.md ├── VERSION ├── conda_completions.py ├── container_select.py ├── images ├── conda-completion.png ├── container-command-quick-menu.png ├── goto-process-definition.png ├── include-process-command-quick-menu.png ├── nfcore-sublime_logo.png ├── params-popup-nf-core-viralrecon.png ├── process-out-completion-nf-core-viralrecon.png ├── process-out-popup-nf-core-viralrecon.png ├── syntax-highlighting-module-imports-nf-core-viralrecon.png ├── syntax-highlighting-process-def-nf-core-viralrecon.png └── syntax-highlighting-workflow-def-nf-core-viralrecon.png ├── messages.json ├── messages ├── 1.0.0.txt ├── 1.1.0.txt ├── 1.2.0.txt └── install.txt ├── nextflow_include_command.py ├── params_completions.py ├── process_label_completion.py ├── process_popups.py ├── snippets ├── nextflow-ch_versions-mix.sublime-snippet ├── nextflow-conda.sublime-snippet ├── nextflow-done-log-oncomplete-onerror.sublime-snippet ├── nextflow-dsl2.sublime-snippet ├── nextflow-env.sublime-snippet ├── nextflow-log-info.sublime-snippet ├── nextflow-modules-config.sublime-snippet ├── nextflow-paired-end-illumina-reads-input-channel.sublime-snippet ├── nextflow-process.sublime-snippet ├── nextflow-publishDir.sublime-snippet ├── nextflow-script-cpus.sublime-snippet ├── nextflow-script-memory-gigabytes.sublime-snippet ├── nextflow-script-memory-megabytes.sublime-snippet ├── nextflow-tag.sublime-snippet └── nextflow-versions-yml.sublime-snippet └── syntax_test_.Nextflow /.gitignore: -------------------------------------------------------------------------------- 1 | package-metadata.json 2 | *.swp 3 | .idea/ 4 | .ipynb_checkpoints/ 5 | *.ipynb 6 | *.pickle 7 | venv/ 8 | .venv/ 9 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 -------------------------------------------------------------------------------- /Comments.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | scope 5 | source.nextflow 6 | settings 7 | 8 | shellVariables 9 | 10 | 11 | name 12 | TM_COMMENT_START 13 | value 14 | // 15 | 16 | 17 | name 18 | TM_COMMENT_START_2 19 | value 20 | /* 21 | 22 | 23 | name 24 | TM_COMMENT_END_2 25 | value 26 | */ 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | /* 2 | sublime-nextflow keymap for Linux 3 | */ 4 | [ 5 | { 6 | "keys": ["ctrl+shift+g"], 7 | "command": "goto_definition" 8 | }, 9 | { 10 | "keys": ["ctrl+l", "p"], 11 | "context": [ 12 | {"key": "selector", "operator": "equal", "operand": "source.nextflow - (comment | meta)", "match_all": true}, 13 | {"key": "selection_empty", "operator": "equal", "operand": true, "match_all": true} 14 | ], 15 | "command": "nextflow_include_process" 16 | }, 17 | { 18 | "keys": ["ctrl+l", "f"], 19 | "context": [ 20 | {"key": "selector", "operator": "equal", "operand": "source.nextflow - (comment | meta)", "match_all": true}, 21 | {"key": "selection_empty", "operator": "equal", "operand": true, "match_all": true} 22 | ], 23 | "command": "nextflow_include_functions" 24 | }, 25 | { 26 | "keys": ["ctrl+l", "c"], 27 | "context": [ 28 | {"key": "selector", "operator": "equal", "operand": "(source.nextflow meta.definition.process.nextflow) - (comment)", "match_all": true}, 29 | {"key": "selection_empty", "operator": "equal", "operand": true, "match_all": true} 30 | ], 31 | "command": "nextflow_biocontainer_select" 32 | } 33 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Peter Kruczkiewicz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Nextflow.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | {"caption": "Nextflow: Fetch Biocontainers information", "command": "nextflow_biocontainer_info_fetch"}, 3 | {"caption": "Nextflow: Fetch Conda packages information", "command": "nextflow_conda_packages_info_fetch"} 4 | ] -------------------------------------------------------------------------------- /Nextflow.sublime-settings: -------------------------------------------------------------------------------- 1 | // These settings override both User and Default settings for the Nextflow syntax 2 | { 3 | "tab_size": 2, 4 | "translate_tabs_to_spaces": true 5 | } 6 | -------------------------------------------------------------------------------- /Nextflow.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | name: Nextflow 5 | file_extensions: 6 | - nf 7 | - nextflow 8 | variables: 9 | unicode_letter: |- 10 | (?:(?xi) 11 | # Valid unicode letters according to: 12 | # http://groovy-lang.org/syntax.html#_normal_identifiers 13 | # Literal Unicode Escaped Unicode 14 | [\x{00C0}-\x{00D6}] | \\u00C[0-9A-F] | \\u00D[0-6] 15 | | [\x{00D8}-\x{00F6}] | \\u00D[89A-F] | \\u00E[0-9A-F] | \\u00F[0-6] 16 | | [\x{00F8}-\x{00FF}] | \\u00F[89A-F] 17 | | [\x{0100}-\x{FFFE}] | \\u0[1-9A-F][0-9A-F]{2} | \\u(?!FFFF)[1-9A-F][0-9A-F]{3} 18 | ) 19 | 20 | # dollars aren't allowed in the single dollar interpolated identifiers 21 | # (dotted expressions), but they are supposed to be valid characters 22 | # in identifiers in other contexts 23 | # e.g. `"$$a"` is invalid, but `"${$a}"` is fine. 24 | single_dollar_interpolation_identifier: (?:{{unicode_letter}}|[a-zA-Z_])(?:{{unicode_letter}}|[a-zA-Z0-9_])* 25 | scope: source.nextflow 26 | contexts: 27 | # else-pop and immediately-pop from https://forum.sublimetext.com/t/syntax-definitions-how-to-force-pop-out-of-the-stack/36376/4 28 | # convenience contexts for popping context off stack 29 | else-pop: 30 | - match: (?=\S) 31 | pop: true 32 | immediately-pop: 33 | - match: '' 34 | pop: true 35 | main: 36 | - match: ^(#!).+$\n 37 | scope: comment.line.hashbang.nextflow 38 | captures: 39 | 1: punctuation.definition.comment.nextflow 40 | - match: '^\s*(package)\s+([^ ;]+)' 41 | scope: meta.package.nextflow 42 | captures: 43 | 1: keyword.other.package.nextflow 44 | 2: storage.type.package.nextflow 45 | - include: nextflow 46 | import-modules: 47 | - match: \b(include)\b 48 | scope: keyword.control.include-module.nextflow 49 | push: 50 | - meta_scope: meta.import-module.nextflow 51 | - include: import-modules-include-body 52 | - include: import-modules-from 53 | - include: import-modules-addparams 54 | - match: $ 55 | pop: true 56 | import-modules-addparams: 57 | - match: \b(addParams)\b 58 | scope: keyword.other.addparams.nextflow 59 | - match: \( 60 | push: 61 | - meta_scope: meta.import-module.addparams-call.nextflow 62 | - match: \) 63 | pop: true 64 | - match: '\b(\w+)(:)' 65 | captures: 66 | 1: variable.other.addparams-key.nextflow 67 | 2: punctuation.separator.annotation.parameter.nextflow 68 | - include: nextflow-code-minus-map-keys 69 | import-modules-from: 70 | - match: \b(from)\b 71 | scope: keyword.control.include-module.from.nextflow 72 | - include: import-from-path 73 | import-from-path: 74 | - match: "'" 75 | scope: punctuation.definition.string.begin.nextflow 76 | push: 77 | - meta_scope: string.quoted.single.relative-import-path.nextflow 78 | - match: (?<=')(\.) 79 | scope: punctuation.definition.relative-import-path.nextflow 80 | - match: (?<=')([^\.]) 81 | scope: invalid.illegal.relative-import-path.nextflow 82 | - match: "'" 83 | scope: punctuation.definition.string.end.nextflow 84 | pop: true 85 | - match: '\n' 86 | scope: invalid.illegal.unclosed-string.nextflow 87 | pop: true 88 | - include: string-escape-sequences 89 | import-modules-include-body: 90 | - meta_scope: meta.statement.include-module.nextflow 91 | - match: \{ 92 | scope: punctuation.section.module-include-block.begin.nextflow 93 | push: 94 | - meta_scope: meta.module-include-block.nextflow 95 | - match: \} 96 | scope: punctuation.section.module-include-block.end.nextflow 97 | pop: true 98 | - match: \b(as)\b 99 | scope: keyword.operator.as.nextflow 100 | - match: ';' 101 | scope: punctuation.separator.include-module-list.nextflow 102 | - match: \b(\w+)\b 103 | scope: entity.name.class.module-import.nextflow 104 | - match: \s*$ 105 | scope: invalid.illegal.unclosed-include-body.nextflow 106 | pop: true 107 | import-modules-from-body: 108 | - meta_scope: meta.statement.include-module.nextflow 109 | - meta_content_scope: meta.include-source.nextflow 110 | class-object: 111 | - match: |- 112 | (?x) 113 | \b( 114 | (?:[a-z]\w*\.)* # Optional package specification 115 | [A-Z]\w+\b # Class name 116 | (?:<(?:[\w, ]*)>)? # Optional Generics 117 | (?:\[\s*\])* # Optional brackets (array) 118 | )\b 119 | scope: storage.type.class.nextflow 120 | classes: 121 | - match: |- 122 | (?x)^\s* 123 | (?:(?:\b(?:(public|private|protected)|(static)|(final)|(native|synchronized|abstract|threadsafe|transient))\b\s*)*) # modifier 124 | (class)\s+ 125 | (\w+)\s* # identifier 126 | captures: 127 | 1: storage.modifier.access-control.nextflow 128 | 2: storage.modifier.static.nextflow 129 | 3: storage.modifier.final.nextflow 130 | 4: storage.modifier.other.nextflow 131 | 5: storage.type.class.nextflow 132 | 6: entity.name.type.class.nextflow 133 | push: 134 | - meta_scope: meta.definition.class.nextflow 135 | - match: $ 136 | captures: 137 | 1: storage.modifier.access-control.nextflow 138 | 2: storage.modifier.static.nextflow 139 | 3: storage.modifier.final.nextflow 140 | 4: storage.modifier.other.nextflow 141 | 5: storage.type.class.nextflow 142 | 6: entity.name.type.class.nextflow 143 | pop: true 144 | - match: '(extends)\s+([a-zA-Z0-9_\.]+(?:<(?:[a-zA-Z0-9_, ])+>)?)\s*' 145 | scope: meta.definition.class.inherited.classes.nextflow 146 | captures: 147 | 1: storage.modifier.extends.nextflow 148 | 2: entity.other.inherited-class.nextflow 149 | - match: (implements)\s 150 | captures: 151 | 1: storage.modifier.implements.nextflow 152 | push: 153 | - meta_scope: meta.definition.class.implemented.interfaces.nextflow 154 | - match: '(?=\s*extends|$|\{)' 155 | pop: true 156 | - match: '((?:[a-z]\w*.)*[A-Z]\w*)\s*(?:(,)|$|\{)' 157 | captures: 158 | 1: entity.other.inherited-class.interface.nextflow 159 | 2: punctuation.definition.implemented.interfaces.separator.nextflow 160 | comment-block: 161 | - match: /\* 162 | scope: punctuation.definition.comment.nextflow 163 | push: 164 | - meta_scope: comment.block.nextflow 165 | - match: \*/ 166 | scope: punctuation.definition.comment.nextflow 167 | pop: true 168 | comments: 169 | - match: /\*\*/ 170 | scope: comment.block.empty.nextflow punctuation.definition.comment.nextflow 171 | - include: scope:text.html.javadoc 172 | - include: comment-block 173 | - match: (//).*$\n? 174 | scope: comment.line.double-slash.nextflow 175 | captures: 176 | 1: punctuation.definition.comment.nextflow 177 | constants: 178 | - match: '\b([A-Z][A-Z0-9_]+)\b' 179 | scope: constant.other.nextflow 180 | - match: \b(true|false|null)\b 181 | scope: constant.language.nextflow 182 | nextflow: 183 | - include: classes 184 | - include: methods 185 | - include: nextflow-code 186 | nextflow-code: 187 | - include: nextflow-code-minus-map-keys 188 | - include: map-keys 189 | - include: block 190 | nextflow-code-minus-map-keys: 191 | - include: import-modules 192 | - include: comments 193 | - include: support-functions 194 | - include: keyword-language 195 | - include: values 196 | - include: keyword-operator 197 | - include: storage-types 198 | - include: storage-modifiers 199 | keyword: 200 | - include: keyword-operator 201 | - include: keyword-language 202 | keyword-language: 203 | - match: \b(try|catch|finally|throw)\b 204 | scope: keyword.control.exception.nextflow 205 | - match: \b(return|break|continue|default|do|while|for|switch|if|else)\b 206 | scope: keyword.control.nextflow 207 | - match: \bcase\b 208 | scope: keyword.control.nextflow 209 | push: 210 | - meta_scope: meta.case.nextflow 211 | - match: ":" 212 | scope: punctuation.definition.case-terminator.nextflow 213 | pop: true 214 | - include: nextflow-code-minus-map-keys 215 | - match: \b(new)\b 216 | scope: keyword.other.new.nextflow 217 | - match: \b(assert)\s 218 | captures: 219 | 1: keyword.control.assert.nextflow 220 | push: 221 | - meta_scope: meta.declaration.assertion.nextflow 222 | - match: $ 223 | pop: true 224 | - match: ":" 225 | scope: keyword.operator.assert.expression-separator.nextflow 226 | - include: nextflow-code-minus-map-keys 227 | - match: \b(throws)\b 228 | scope: keyword.other.throws.nextflow 229 | keyword-operator: 230 | - match: \b(as)\b 231 | scope: keyword.operator.as.nextflow 232 | - match: \b(is)\b 233 | scope: keyword.operator.is.nextflow 234 | - match: '\?\:' 235 | scope: keyword.operator.elvis.nextflow 236 | - match: \.\. 237 | scope: keyword.operator.range.nextflow 238 | - match: \-> 239 | scope: keyword.operator.arrow.nextflow 240 | - match: "<<" 241 | scope: keyword.operator.leftshift.nextflow 242 | - match: (?<=\S)\.(?=\S) 243 | scope: punctuation.accessor.dot.nextflow 244 | - match: (?<=\S)\?\.(?=\S) 245 | scope: punctuation.accessor.nextflow 246 | - match: \? 247 | scope: keyword.operator.ternary.nextflow 248 | push: 249 | - meta_scope: meta.evaluation.ternary.nextflow 250 | - match: ":" 251 | scope: keyword.operator.ternary.expression-separator.nextflow 252 | - match: \} 253 | pop: true 254 | - match: $ 255 | pop: true 256 | - include: nextflow-code 257 | - include: else-pop 258 | - match: "==~" 259 | scope: keyword.operator.match.nextflow 260 | - match: "=~" 261 | scope: keyword.operator.find.nextflow 262 | - match: \b(instanceof)\b 263 | scope: keyword.operator.instanceof.nextflow 264 | - match: (===|==|!=|<=|>=|<=>|<>|<|>|<<) 265 | scope: keyword.operator.comparison.nextflow 266 | - match: "=" 267 | scope: keyword.operator.assignment.nextflow 268 | - match: (\-\-|\+\+) 269 | scope: keyword.operator.increment-decrement.nextflow 270 | - match: (\-|\+|\*|\/|%) 271 | scope: keyword.operator.arithmetic.nextflow 272 | - match: (!|&&|\|\|) 273 | scope: keyword.operator.logical.nextflow 274 | map-keys: 275 | - match: (\w+)\s*(:) 276 | captures: 277 | 1: constant.other.key.nextflow 278 | 2: punctuation.definition.separator.key-value.nextflow 279 | method-call: 280 | - match: (\w+)(\() 281 | captures: 282 | 1: meta.method.nextflow 283 | 2: punctuation.definition.method-parameters.begin.nextflow 284 | push: 285 | - meta_scope: meta.method-call.nextflow 286 | - match: \) 287 | scope: punctuation.definition.method-parameters.end.nextflow 288 | pop: true 289 | - match: "," 290 | scope: punctuation.definition.separator.parameter.nextflow 291 | - include: nextflow-code 292 | process-def: 293 | - match: ^\s*(process)\s+(\w+|"[^"]+"|'[^']+')\s*(\{) 294 | captures: 295 | 1: storage.type.return-type.def.nextflow 296 | 2: entity.name.class.process.nextflow 297 | 3: punctuation.definition.process.begin.nextflow 298 | push: 299 | - meta_scope: meta.definition.process.nextflow 300 | - match: \} 301 | captures: 302 | 1: punctuation.definition.process.end.nextflow 303 | pop: true 304 | - include: process-script-def 305 | - include: process-script-single-quote 306 | - include: process-script-double-quote 307 | - include: process-output 308 | - include: process-input 309 | - include: directives 310 | - include: nextflow-code 311 | directives: 312 | - match: '^\s+(afterScript|beforeScript|cache|cpus|container|containerOptions|clusterOptions|disk|echo|errorStrategy|executor|ext|label|maxErrors|maxForks|maxRetries|memory|module|penv|pod|publishDir|queue|scratch|stageInMode|stageOutMode|storeDir|tag|time|validExitStatus):' 313 | scope: invalid.illegal.directive-with.nextflow 314 | - match: ^\s+(afterScript|beforeScript|cache|cpus|container|containerOptions|clusterOptions|disk|echo|errorStrategy|executor|ext|label|maxErrors|maxForks|maxRetries|memory|module|penv|pod|publishDir|queue|scratch|stageInMode|stageOutMode|storeDir|tag|time|validExitStatus)\b 315 | captures: 316 | 1: support.type.nextflow 317 | - match: '^\s*(conda)\s+' 318 | captures: 319 | 1: support.type.conda.nextflow 320 | push: 321 | - meta_scope: meta.definition.conda-directive.nextflow 322 | - include: nextflow-code 323 | - match: '$' 324 | pop: true 325 | process-script-def: 326 | - match: '^\s*(script|shell|exec):' 327 | captures: 328 | 1: keyword.other.process-script-def.nextflow 329 | process-input: 330 | - match: '^\s*(input)\:' 331 | captures: 332 | 1: keyword.other.process-input.nextflow 333 | push: 334 | - meta_scope: meta.definition.process-input.nextflow 335 | - include: process-input-channel 336 | - include: nextflow-code 337 | - match: '(?=^\s*output\:)' 338 | pop: true 339 | - include: popping-process-input-or-output 340 | process-output: 341 | - match: '^\s*(output)\:' 342 | captures: 343 | 1: keyword.other.process-output.nextflow 344 | push: 345 | - meta_scope: meta.definition.process-output.nextflow 346 | - include: process-output-channel 347 | - include: nextflow-code 348 | - include: popping-process-input-or-output 349 | popping-process-input-or-output: 350 | - match: (?=^\s*''') 351 | pop: true 352 | - match: (?=^\s*""") 353 | pop: true 354 | - match: (?=^\s*when\:) 355 | pop: true 356 | - match: (?=^\s*(script|shell|exec)\:) 357 | pop: true 358 | process-input-channel: 359 | - match: '^\s*(?=tuple|set|file|path|val)' 360 | push: 361 | - meta_scope: meta.definition.process-input-channel.nextflow 362 | - include: process-tuple-operator 363 | - include: process-set-operator 364 | - include: process-val 365 | - include: process-path 366 | - include: process-file 367 | - match: \b(from)\b 368 | scope: keyword.operator.from.nextflow 369 | - match: \b(into)\b 370 | scope: invalid.illegal.into.nextflow 371 | - match: '\b[^a-zA-Z0-9_]\s$' 372 | scope: invalid.illegal.process-input-channel.nextflow 373 | pop: true 374 | - match: $ 375 | pop: true 376 | process-output-channel: 377 | - match: '^\s*(?=tuple|set|file|path|val)' 378 | push: 379 | - meta_scope: meta.definition.process-output-channel.nextflow 380 | - include: process-tuple-operator 381 | - include: process-set-operator 382 | - include: process-val 383 | - include: process-path 384 | - include: process-file 385 | - include: process-output-emit 386 | - match: \b(into)\b 387 | scope: keyword.operator.into.nextflow 388 | - match: \b(from)\b 389 | scope: invalid.illegal.from.nextflow 390 | - match: '\b[^a-zA-Z0-9_]\s$' 391 | scope: invalid.illegal.process-output-channel.nextflow 392 | pop: true 393 | - match: $ 394 | pop: true 395 | process-output-emit: 396 | - match: (,)\s*(emit)(:)\s*(\w+) 397 | captures: 398 | 1: punctuation.separator.process-output-channel.nextflow 399 | 2: keyword.other.process-output-emit.nextflow 400 | 3: punctuation.separator.process-output-emit.nextflow 401 | 4: variable.other.process-output-emit.nextflow 402 | pop: true 403 | process-set-operator: 404 | - match: \b(set)\b 405 | captures: 406 | 1: keyword.other.channel.set.nextflow 407 | process-tuple-operator: 408 | - match: \b(tuple)\b 409 | captures: 410 | 1: keyword.other.channel.tuple.nextflow 411 | process-val: 412 | - match: \b(val)\b 413 | scope: support.function.val.nextflow 414 | push: 415 | - meta_scope: meta.function-call.process-val.nextflow 416 | - include: popping-process-channel-func 417 | process-path: 418 | - match: \b(path)\b 419 | scope: support.function.path.nextflow 420 | push: 421 | - meta_scope: meta.function-call.process-path.nextflow 422 | - include: popping-process-channel-func 423 | process-file: 424 | - match: \b(file)\b 425 | scope: support.function.file.nextflow 426 | push: 427 | - meta_scope: meta.function-call.process-file.nextflow 428 | - include: popping-process-channel-func 429 | popping-process-channel-func: 430 | - match: \) 431 | pop: true 432 | - match: \b(?=from|into)\b 433 | pop: true 434 | - match: (?=,) 435 | pop: true 436 | - include: string-quoted-double 437 | - include: string-quoted-single 438 | - match: '\b(\w+)\b' 439 | scope: variable.other.process.nextflow 440 | - match: $ 441 | pop: true 442 | process-script-double-quote: 443 | - match: ^\s*(""") 444 | captures: 445 | 1: punctuation.definition.string.begin.nextflow 446 | push: 447 | - meta_scope: string.quoted.double.block.nextflow 448 | - match: '""""' 449 | scope: invalid.illegal 450 | pop: true 451 | - match: '"""' 452 | scope: punctuation.definition.string.end.nextflow 453 | pop: true 454 | - match: '(?=#!/usr/bin/env python)' 455 | push: Packages/Python/Python.sublime-syntax 456 | with_prototype: 457 | - match: (?=""") 458 | pop: true 459 | - include: string-escape-sequences 460 | - include: single-dollar-string-interpolation 461 | - match: '\$\{' 462 | scope: punctuation.section.embedded.nextflow 463 | push: 464 | - meta_scope: source.nextflow.embedded.source 465 | - match: '\}' 466 | scope: punctuation.section.embedded.nextflow 467 | pop: true 468 | - include: escaped-end-of-line 469 | - include: nextflow-code 470 | - match: (task)\.(\w+) 471 | captures: 472 | 1: support.variable.task.nextflow 473 | 2: entity.name.task-param.nextflow 474 | # anything else following a dollar sign is not a valid interpolation 475 | - match: \$(?=") 476 | scope: invalid.illegal.nextflow 477 | - match: \$[^"]+ 478 | scope: invalid.illegal.nextflow 479 | process-script-single-quote: 480 | - match: ^\s*(''') 481 | captures: 482 | 1: punctuation.definition.string.begin.nextflow 483 | push: 484 | - meta_scope: string.quoted.single.block.nextflow 485 | - match: "'''" 486 | scope: punctuation.definition.string.end.nextflow 487 | pop: true 488 | - include: string-escape-sequences 489 | conditional-block: 490 | - match: \( 491 | push: 492 | - meta_scope: meta.statement.conditional.nextflow 493 | - match: \) 494 | pop: true 495 | - include: nextflow-code-minus-map-keys 496 | 497 | workflow-conditionals: 498 | - match: \b(if)\b 499 | scope: keyword.control.conditional.if.nextflow 500 | - include: conditional-block 501 | - match: \{ 502 | push: 503 | - meta_scope: meta.conditional-block.nextflow 504 | - match: \} 505 | pop: true 506 | - include: workflow-stuff 507 | workflow-process: 508 | - match: \b([A-Z][A-Z0-9\_]+) 509 | scope: entity.name.class.process.nextflow 510 | - match: \( 511 | scope: punctuation.section.process-call.begin.nextflow 512 | push: 513 | - meta_scope: meta.process-call.nextflow 514 | - match: \) 515 | scope: punctuation.section.arguments.end.nextflow 516 | pop: true 517 | - include: workflow-process 518 | - include: nextflow-code 519 | - match: \.(out) 520 | scope: keyword.process.out.nextflow 521 | push: 522 | - match: \s* 523 | - match: (\.)?(\w+)? 524 | captures: 525 | 1: punctuation.accessor.dot.process-out.nextflow 526 | 2: variable.channel.process-output-emit.nextflow 527 | pop: true 528 | workflow-def: 529 | - match: ^\s*(workflow)\s*(\w+)?.*(\{) 530 | captures: 531 | 1: keyword.declaration.workflow.nextflow 532 | 2: entity.name.workflow.nextflow 533 | 3: punctuation.definition.workflow.begin.nextflow 534 | push: 535 | - meta_scope: meta.definition.workflow.nextflow 536 | - include: workflow-stuff 537 | workflow-stuff: 538 | - match: \} 539 | captures: 540 | 1: punctuation.definition.workflow.end.nextflow 541 | pop: true 542 | - include: workflow-process 543 | - include: workflow-process-call 544 | - include: workflow-conditionals 545 | - include: nextflow-code 546 | 547 | method-declaration-remainder: 548 | - match: \( 549 | scope: punctuation.definition.parameters.begin.nextflow 550 | push: 551 | - meta_content_scope: meta.definition.method.parameters.nextflow 552 | - match: \) 553 | scope: punctuation.definition.parameters.end.nextflow 554 | pop: true 555 | - match: |- 556 | (?x)\s* 557 | ( 558 | (?:boolean|byte|char|short|int|float|long|double|(?:\w+\.)*[A-Z]\w*\b(?:<(?:[\w, ]*)>)?(?:\[\s*\])*) 559 | )? 560 | \s* 561 | ([a-z_][A-Za-z0-9_]*) # variable 562 | scope: meta.definition.method.parameter.nextflow 563 | captures: 564 | 1: storage.type.parameter.nextflow 565 | 2: variable.parameter.nextflow 566 | - match: '(boolean|byte|char|short|int|float|long|double|(?:\w+\.)*[A-Z]\w*\b(?:<(?:[\w, ]*)>)?(?:\[\s*\])*)' 567 | scope: meta.definition.method.parameter.nextflow 568 | captures: 569 | 1: storage.type.parameter.nextflow 570 | - match: "," 571 | scope: punctuation.definition.parameters.separator.nextflow 572 | - include: comment-block 573 | - match: (?<=\))\s*(throws)\s 574 | captures: 575 | 1: storage.modifier.throws.nextflow 576 | push: 577 | - meta_scope: meta.definition.method.throwables.nextflow 578 | - match: '(?=$|\{)' 579 | captures: 580 | 1: storage.modifier.throws.nextflow 581 | pop: true 582 | - match: '((?:[a-z]\w*.)*[A-Z]\w*)\s*(?:(,)|$|\{)' 583 | captures: 584 | 1: storage.type.throwable.nextflow 585 | 2: punctuation.definition.throwables.separator.nextflow 586 | methods: 587 | - match: |- 588 | (?x)^\s* 589 | (?: # zero or more modifiers 590 | (?: 591 | (public|private|protected)|(final)|(native|synchronized|abstract|threadsafe|transient) 592 | ) 593 | \s+ 594 | )? 595 | \s* 596 | ([A-Z](?:[a-zA-Z0-9_])+) # constructor/class name 597 | \s* 598 | (?=\() 599 | captures: 600 | 1: storage.modifier.access-control.nextflow 601 | 2: storage.modifier.final.nextflow 602 | 3: storage.modifier.other.nextflow 603 | 4: entity.name.function.constructor.nextflow 604 | 5: punctuation.definition.parameters.begin.nextflow 605 | push: 606 | - meta_scope: meta.definition.constructor.nextflow 607 | - match: '{|$\n?' 608 | pop: true 609 | - include: method-declaration-remainder 610 | - match: |- 611 | (?x)^\s* 612 | (?: 613 | (?: # or modifier and optional type 614 | (?:(?:\b(public|private|protected)|(static)|(final)|(native|synchronized|abstract|threadsafe|transient))\b\s+)+\s* # modifier 615 | (?:\b 616 | (void\b) 617 | | 618 | ((?:boolean|byte|char|short|int|float|long|double)\b) # primitive 619 | | 620 | ( # or class type 621 | (?:\w+\.)*[A-Z]\w+\b # Class name 622 | (?:<(?:[\w, ]*)>)? # optional Generic type 623 | (?:\[\s*\])* # zero or more square brackets (array) 624 | ) 625 | )? 626 | ) 627 | | 628 | (?:\b # or type by itself 629 | (def\b) 630 | | 631 | (void\b) 632 | | 633 | ((?:boolean|byte|char|short|int|float|long|double)\b) # primitive 634 | | 635 | ( # or class type 636 | (?:\w+\.)*[A-Z]\w+\b # Class name 637 | (?:<(?:[\w, ]*)>)? # optional generics info 638 | (?:\[\s*\])* # zero or more square brackets (array) 639 | ) 640 | ) 641 | ) 642 | \s* 643 | (\w+) # method name 644 | \s* 645 | (?=\() # opening parens 646 | captures: 647 | 1: storage.modifier.access-control.nextflow 648 | 2: storage.modifier.static.nextflow 649 | 3: storage.modifier.final.nextflow 650 | 4: storage.modifier.other.nextflow 651 | 5: storage.type.return-type.void.nextflow 652 | 6: storage.type.return-type.primitive.nextflow 653 | 7: storage.type.return-type.class.nextflow 654 | 8: storage.type.return-type.def.nextflow 655 | 9: storage.type.return-type.void.nextflow 656 | 10: storage.type.return-type.primitive.nextflow 657 | 11: storage.type.return-type.class.nextflow 658 | 12: entity.name.function.nextflow 659 | push: 660 | - meta_scope: meta.definition.method.nextflow 661 | - match: '{|$\n?' 662 | pop: true 663 | - include: method-declaration-remainder 664 | block: 665 | - match: '\{' 666 | scope: punctuation.section.block.begin.nextflow 667 | push: 668 | - meta_scope: meta.block.nextflow 669 | - match: '\}' 670 | scope: punctuation.section.block.end.nextflow 671 | pop: true 672 | - include: nextflow-code 673 | numbers: 674 | - match: '((0(x|X)[0-9a-fA-F]*)|(\+|-)?\b(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFfUuDd]|UL|ul)?\b' 675 | scope: constant.numeric.nextflow 676 | regexp: 677 | - match: "/(?=[^/]+/)" 678 | scope: punctuation.definition.string.regexp.begin.nextflow 679 | push: 680 | - meta_scope: string.regexp.nextflow 681 | - match: / 682 | scope: punctuation.definition.string.regexp.end.nextflow 683 | pop: true 684 | # backslashes only escape forward slashes and newlines (and unicode) 685 | - match: \\/ 686 | scope: constant.character.escape.nextflow 687 | - include: escaped-end-of-line 688 | - include: unicode-escape-sequence 689 | - include: single-dollar-string-interpolation 690 | - match: '\$\{' 691 | scope: punctuation.section.embedded.nextflow 692 | push: 693 | - meta_scope: source.nextflow.embedded.source 694 | - match: '\}' 695 | scope: punctuation.section.embedded.nextflow 696 | pop: true 697 | - include: escaped-end-of-line 698 | # newlines are invalid inside the interpolation 699 | # but outside of a nested multiline string 700 | - match: '\n' 701 | scope: invalid.illegal.newline.nextflow 702 | pop: true 703 | - include: nextflow-code 704 | storage-modifiers: 705 | - match: \b(private|protected|public)\b 706 | scope: storage.modifier.access-control.nextflow 707 | - match: \b(static)\b 708 | scope: storage.modifier.static.nextflow 709 | - match: \b(final)\b 710 | scope: storage.modifier.final.nextflow 711 | - match: \b(native|synchronized|abstract|threadsafe|transient)\b 712 | scope: storage.modifier.other.nextflow 713 | storage-types: 714 | - match: '(@[^ (]+)(\()' 715 | captures: 716 | 1: storage.type.annotation.nextflow 717 | 2: punctuation.definition.annotation-arguments.begin.nextflow 718 | push: 719 | - meta_scope: meta.declaration.annotation.nextflow 720 | - match: (\)) 721 | captures: 722 | 1: punctuation.definition.annotation-arguments.end.nextflow 723 | pop: true 724 | - match: (\w*)\s*(=) 725 | captures: 726 | 1: constant.other.key.nextflow 727 | 2: keyword.operator.assignment.nextflow 728 | - include: values 729 | - match: "," 730 | scope: punctuation.definition.separator.nextflow 731 | - match: '@\S+' 732 | scope: storage.type.annotation.nextflow 733 | - match: \b(def)\b 734 | scope: storage.type.def.nextflow 735 | - match: '\b(boolean|byte|char|short|int|float|long|double)(?:\[\s*\])*\b' 736 | scope: storage.type.primitive.nextflow 737 | single-dollar-string-interpolation: 738 | - match: \${{single_dollar_interpolation_identifier}} 739 | scope: variable.other.interpolated.nextflow 740 | push: 741 | - match: \.(?={{single_dollar_interpolation_identifier}}) 742 | scope: punctuation.accessor.dot.nextflow 743 | - match: '{{single_dollar_interpolation_identifier}}' 744 | scope: variable.other.interpolated.nextflow 745 | - match: \b 746 | pop: true 747 | unicode-escape-sequence: 748 | - match: \\u\h{4} 749 | scope: constant.character.escape.nextflow 750 | - match: \\u(?!\h{4}).{4} 751 | scope: invalid.illegal.escape.nextflow 752 | escaped-end-of-line: 753 | - match: \\\n 754 | scope: constant.character.escape.nextflow 755 | string-escape-sequences: 756 | - include: unicode-escape-sequence 757 | - include: escaped-end-of-line 758 | - match: |- 759 | \\[nrtbf\$\\'"] 760 | scope: constant.character.escape.nextflow 761 | - match: \\. 762 | scope: invalid.illegal.escape.nextflow 763 | string-quoted-double: 764 | - match: '"' 765 | scope: punctuation.definition.string.begin.nextflow 766 | push: 767 | - meta_scope: string.quoted.double.nextflow 768 | - match: '"' 769 | scope: punctuation.definition.string.end.nextflow 770 | pop: true 771 | - match: '\n' 772 | scope: invalid.illegal.unclosed-string.nextflow 773 | pop: true 774 | - include: string-escape-sequences 775 | - include: single-dollar-string-interpolation 776 | - match: '\$\{' 777 | scope: punctuation.section.embedded.nextflow 778 | push: 779 | - meta_scope: source.nextflow.embedded.source 780 | - match: '\}' 781 | scope: punctuation.section.embedded.nextflow 782 | pop: true 783 | - include: escaped-end-of-line 784 | # we don't consume the newline here, so that 785 | # the outer scope handles it and pops correctly 786 | - match: '(?=\n)' 787 | pop: true 788 | - include: nextflow-code 789 | # anything else following a dollar sign is not a valid interpolation 790 | - match: \$(?=") 791 | scope: invalid.illegal.identifier.nextflow 792 | - match: \$[^"]+ 793 | scope: invalid.illegal.identifier.nextflow 794 | string-quoted-single: 795 | - match: "'" 796 | scope: punctuation.definition.string.begin.nextflow 797 | push: 798 | - meta_scope: string.quoted.single.nextflow 799 | - match: "'" 800 | scope: punctuation.definition.string.end.nextflow 801 | pop: true 802 | - match: '\n' 803 | scope: invalid.illegal.unclosed-string.nextflow 804 | pop: true 805 | - include: string-escape-sequences 806 | string-quoted-triple-single: 807 | - match: "'''" 808 | scope: punctuation.definition.string.begin.nextflow 809 | push: 810 | - meta_scope: string.quoted.single.block.nextflow 811 | - match: "'''" 812 | scope: punctuation.definition.string.end.nextflow 813 | pop: true 814 | - include: string-escape-sequences 815 | string-quoted-triple-double: 816 | - match: '"""' 817 | scope: punctuation.definition.string.begin.nextflow 818 | push: 819 | - meta_scope: string.quoted.double.block.nextflow 820 | - match: '""""' 821 | scope: invalid.illegal 822 | pop: true 823 | - match: '"""' 824 | scope: punctuation.definition.string.end.nextflow 825 | pop: true 826 | - include: string-escape-sequences 827 | - include: single-dollar-string-interpolation 828 | - match: '\$\{' 829 | scope: punctuation.section.embedded.nextflow 830 | push: 831 | - meta_scope: source.nextflow.embedded.source 832 | - match: '\}' 833 | scope: punctuation.section.embedded.nextflow 834 | pop: true 835 | - include: escaped-end-of-line 836 | - include: nextflow-code 837 | # anything else following a dollar sign is not a valid interpolation 838 | - match: \$(?=") 839 | scope: invalid.illegal.nextflow 840 | - match: \$[^"]+ 841 | scope: invalid.illegal.nextflow 842 | string-dollar-slashy: 843 | - clear_scopes: 1 844 | - match: '\$/' 845 | scope: punctuation.definition.string.begin.nextflow 846 | push: 847 | - meta_scope: string.quoted.other.dollar-slashy.nextflow 848 | - match: '/\$' 849 | scope: punctuation.definition.string.end.nextflow 850 | pop: true 851 | - match: '\$/|\$\$' 852 | scope: constant.character.escape.nextflow 853 | # backslashes only escape newlines (and unicode) 854 | - include: escaped-end-of-line 855 | - include: unicode-escape-sequence 856 | - include: single-dollar-string-interpolation 857 | - match: '\$\{' 858 | scope: punctuation.section.embedded.nextflow 859 | push: 860 | - meta_scope: source.nextflow.embedded.source 861 | - match: '\}' 862 | scope: punctuation.section.embedded.nextflow 863 | pop: true 864 | - include: escaped-end-of-line 865 | - include: nextflow-code 866 | strings: 867 | - include: string-quoted-triple-double 868 | - include: string-quoted-triple-single 869 | - include: string-quoted-double 870 | - include: string-quoted-single 871 | - include: regexp 872 | - include: string-dollar-slashy 873 | structures: 874 | - match: '\[' 875 | scope: punctuation.definition.structure.begin.nextflow 876 | push: 877 | - meta_scope: meta.structure.nextflow 878 | - match: '\]' 879 | scope: punctuation.definition.structure.end.nextflow 880 | pop: true 881 | - include: nextflow-code 882 | - match: "," 883 | scope: punctuation.definition.separator.nextflow 884 | support-functions: 885 | - match: (?x)\b(?:sprintf|print(?:f|ln)?)\b 886 | scope: support.function.print.nextflow 887 | - match: |- 888 | (?x)\b(?:shouldFail|fail(?:NotEquals)?|ass(?:ume|ert(?:S(?:cript|ame)|N(?:ot(?:Same| 889 | Null)|ull)|Contains|T(?:hat|oString|rue)|Inspect|Equals|False|Length| 890 | ArrayEquals)))\b 891 | scope: support.function.testing.nextflow 892 | - match: (?x)\b(?:sleep|inspect|dump|use|with)\b 893 | scope: support.function.other.nextflow 894 | values: 895 | - include: variables 896 | - include: built-ins 897 | - include: strings 898 | - include: numbers 899 | - include: constants 900 | - include: class-object 901 | - include: structures 902 | - include: method-call 903 | - include: process-def 904 | - include: workflow-def 905 | - include: channel-operators 906 | - include: channels 907 | channel-combining-operators: 908 | - match: \s*(\.)(cross|collectFile|combine|concat|into|join|merge|mix|phase|spread|tap)\s*\( 909 | captures: 910 | 1: punctuation.accessor.channel.nextflow 911 | 2: keyword.operator.channel-combining-operator.nextflow 912 | 3: punctuation.section.channel-operator-call.begin.nextflow 913 | push: 914 | - meta_scope: meta.channel-operator-call 915 | - match: \) 916 | scope: punctuation.section.channel-operator-call.end.nextflow 917 | pop: true 918 | - include: workflow-process 919 | - include: nextflow 920 | 921 | channel-operators: 922 | - match: \.(create|empty|fromFilePairs|fromPath|from|value|watchPath) 923 | captures: 924 | 1: support.function.channel-factory.nextflow 925 | - match: \s+\.(distinct|filter|first|last|randomSample|take|unique|until) 926 | captures: 927 | 1: keyword.operator.channel-filtering-operator.nextflow 928 | - match: \s*\.(buffer|collate|collect|flatten|flatMap|groupBy|groupTuple|map|reduce|toList|toSortedList|transpose) 929 | captures: 930 | 1: keyword.operator.channel-transforming-operator.nextflow 931 | - match: \s*\.(splitCsv|splitFasta|splitFastq|splitText) 932 | captures: 933 | 1: keyword.operator.channel-splitting-operator.nextflow 934 | - include: channel-combining-operators 935 | - match: \s*\.(choice|separate|route) 936 | captures: 937 | 1: keyword.operator.channel-forking-operator.nextflow 938 | - match: \s*\.(toInteger|countBy|count|min|max|sum) 939 | captures: 940 | 1: keyword.operator.channel-maths-operator.nextflow 941 | - match: \s*\.(close|dump|ifEmpty|println|print|set|view)\b 942 | captures: 943 | 1: keyword.operator.channel-other-operator.nextflow 944 | built-ins: 945 | - match: \b(file)\b 946 | scope: support.function.file.nextflow 947 | - match: \b(log)\.\b 948 | captures: 949 | 1: support.variable.log.nextflow 950 | - match: \b(workflow)\.\b 951 | captures: 952 | 1: support.variable.workflow.nextflow 953 | - match: \b(params)(\.)(\w+)\b 954 | captures: 955 | 1: support.variable.params.nextflow 956 | 2: punctuation.params.dot 957 | 3: entity.name.parameter.nextflow 958 | - match: \b(params)(\.) 959 | captures: 960 | 1: support.variable.params.nextflow 961 | 2: punctuation.params.dot 962 | - match: ^\s*(exit)\s+(\d+)\b 963 | captures: 964 | 1: support.function.exit.nextflow 965 | 2: constant.numeric.nextflow 966 | variables: 967 | - match: \b(this|super|it)\b 968 | scope: variable.language.nextflow 969 | channels: 970 | - match: \b(ch_\w+)\b 971 | scope: variable.parameter.channel.nextflow 972 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![nf-core/sublime](images/nfcore-sublime_logo.png) 2 | 3 | ### [Nextflow] and [nf-core] workflow completions, commands, syntax highlighting and snippets for [Sublime Text 4] 4 | 5 | - **⚠️WARNING⚠️:** Sublime Text 3 is no longer supported by this package as of version 1.0.0. Only Sublime Text 4 is supported since this package requires Python 3.8 and new features in ST4. 6 | - **❗NOTE❗:** Only [DSL-2] Nextflow workflows are supported by this package. 7 | 8 | This package provides [Nextflow] workflow language: 9 | 10 | - completions (`params.`, `conda`, `.out.`) 11 | - informative popups for processes and sub-workflows 12 | - commands (insert container directive, insert module import statement) 13 | - syntax highlighting 14 | - snippets 15 | 16 | Essentially, this package tries to make [Nextflow] workflow development a bit easier especially when trying to develop [nf-core] conventions and best practices for developing [Nextflow] pipelines. 17 | 18 | ## Nextflow completions and commands 19 | 20 | ### Process module include command 21 | 22 | - `ctrl+l,p` in an `.nf` file where you want to import your process 23 | 24 | ![](images/include-process-command-quick-menu.png) 25 | 26 | - select the process you wish to import 27 | - something like the following will be inserted 28 | 29 | ```nextflow 30 | include { MAKE_BED_MASK } from '../modules/local/make_bed_mask' addParams( options: modules['make_bed_mask'] ) 31 | ``` 32 | 33 | The `addParams( options: modules['make_bed_mask'] )` may not be needed and can be removed; it assumes that you have a `conf/modules.config` with a map of your module `args`, `publish_dir`, etc (see [nf-core/modules](https://github.com/nf-core/modules#module-parameters) for more info). 34 | 35 | ### Workflow `params` 36 | 37 | **NOTE:** Completions and info popups for `params` depend on a valid `nextflow_schema.json` in your workflow root directory. Example [`nextflow_schema.json` for nf-core/viralrecon workflow](https://github.com/nf-core/viralrecon/blob/master/nextflow_schema.json). 38 | 39 | Navigate cursor to a `params.` to show a popup with info pulled from the `nextflow_schema.json` for that workflow parameter. 40 | 41 | ![](images/params-popup-nf-core-viralrecon.png) 42 | 43 | ### [Conda] completion 44 | 45 | **NOTE:** [Conda] must be installed along with any channels (e.g. [bioconda], [conda-forge]) to get packages information (needs to be able to run `conda search`). 46 | 47 | - Open the command palette (`ctrl+shift+p`) and run the `Nextflow: Fetch Conda packages information` command to fetch the latest Conda package info (runs `conda search`; may take a while). 48 | - In your process definition, inside the `conda` directive string press `ctrl+space` to bring up the completion list. *This may have a little delay since the package list may be very large.* 49 | 50 | ```nextflow 51 | process PANGOLIN { 52 | conda '' 53 | } 54 | ``` 55 | 56 | ![](images/conda-completion.png) 57 | 58 | ### Process output channel completion 59 | 60 | Get process named output (i.e. using the [`emit`](https://www.nextflow.io/docs/latest/dsl2.html#process-named-output) option) completions after typing `.out.`. 61 | 62 | ![](images/process-out-completion-nf-core-viralrecon.png) 63 | 64 | - completion shows what's on the channel as well! 65 | 66 | ### Process output channel popup 67 | 68 | Show useful info about what output the a process is emitting. 69 | 70 | ![](images/process-out-popup-nf-core-viralrecon.png) 71 | 72 | ### Container directive insert command 73 | 74 | This command inserts similar code to what you'd find in an [nf-core modules](https://github.com/nf-core/modules) process definition with respect to process `container` directives. The [Biocontainers] information is pulled from the [Singularity][] images [https://depot.galaxyproject.org/singularity/](https://depot.galaxyproject.org/singularity/) and cached as a Python pickle file. [Docker] container image tags point to the [Biocontainers][] [Quay.io page](https://quay.io/organization/biocontainers). 75 | 76 | - Open the command palette (`ctrl+shift+p`) and run the `Nextflow: Fetch Biocontainers information` command to fetch the latest [Biocontainers] list fetched from 77 | - In your process definition, press `ctrl+l,c`, search for the container you're interested in 78 | 79 | ![](images/container-command-quick-menu.png) 80 | 81 | - Select the program and version you're interested in to output the following: 82 | 83 | ```nextflow 84 | if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) { 85 | container 'https://depot.galaxyproject.org/singularity/fastqc:0.11.9--hdfd78af_1' 86 | } else { 87 | container 'quay.io/biocontainers/fastqc:0.11.9--hdfd78af_1' 88 | } 89 | ``` 90 | 91 | ## Nextflow Syntax Highlighting 92 | 93 | Nextflow syntax highlighting extends Sublime Text 4's Groovy syntax with highlighting of: 94 | 95 | - imports (DSL-2 modules) 96 | ![](images/syntax-highlighting-module-imports-nf-core-viralrecon.png) 97 | - workflow definitions 98 | ![](images/syntax-highlighting-workflow-def-nf-core-viralrecon.png) 99 | - process definitions 100 | ![](images/syntax-highlighting-process-def-nf-core-viralrecon.png) 101 | - channel highlighting based on matching `ch_*` 102 | - some invalid syntax checks (into channel in input tag and from channel in output tag) 103 | - highlighting special Nextflow functions and variables (`workflow`, `params`, `task`, etc) 104 | 105 | Process syntax highlighting and scoping allows one to easily go to the definition or usages of a process (`ctrl+shift+g` keyboard shortcut): 106 | 107 | ![](images/goto-process-definition.png) 108 | 109 | ## Nextflow Snippets 110 | 111 | Type one of the following and press `TAB`: 112 | 113 | - `!env`: `#!/usr/bin/env nextflow` 114 | - `proc`: [process](https://www.nextflow.io/docs/latest/process.html) snippet 115 | - `tag`: [tag](https://www.nextflow.io/docs/latest/process.html#tag) process directive snippet 116 | - `pub`: [publishDir](https://www.nextflow.io/docs/latest/process.html#publishdir) process directive snippet 117 | - `conda`: [conda](https://www.nextflow.io/docs/edge/process.html#conda) process directive snippet 118 | - `illumina`: Illumina paired-end reads file pairs channel 119 | - `cpus`: insert `"${task.cpus}"` into a process script 120 | - `script_path`: specify user script (e.g. Python script) to use from `scripts/` directory in workflow base directory 121 | - `info`: `log.info` snippet 122 | - `done`: on workflow complete or error message 123 | 124 | # Changelog 125 | 126 | ## 1.1.0 - [2022-05-13] 127 | 128 | **Added:** 129 | 130 | - more informative popups for showing info about process output so it's easier to select the correct output channel without referencing the process code. 131 | - subworkflow completions and info popups about `take` and `emit` channels 132 | - `conda` directive snippet 133 | 134 | **Fixed:** 135 | 136 | - comment toggling 137 | 138 | ## 1.0.0 - [2021-06-30] 139 | 140 | - Added syntax highlighting for Nextflow DSL-2 141 | - Added completions and commands for workflow `params`, `PROCESS.out.`, conda, container, module include 142 | - Removed some not very useful snippets 143 | 144 | ## 0.1.0-alpha.1 - [2019-03-27] 145 | 146 | - Initial release with Groovy-based syntax highlighting and basic snippets 147 | 148 | [DSL-2]: https://www.nextflow.io/docs/latest/dsl2.html 149 | [Nextflow]: https://www.nextflow.io/ 150 | [nf-core]: https://nf-co.re/ 151 | [Conda]: https://docs.conda.io/en/latest/ 152 | [bioconda]: https://bioconda.github.io/ 153 | [conda-forge]: https://conda-forge.org/ 154 | [Singularity]: https://sylabs.io/guides/3.7/user-guide/quick_start.html 155 | [Docker]: https://www.docker.com/ 156 | [Sublime Text 4]: http://www.sublimetext.com/ 157 | [Biocontainers]: https://biocontainers.pro/ 158 | 159 | # License 160 | 161 | MIT License 162 | 163 | Copyright (c) Peter Kruczkiewicz 164 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.2.0 -------------------------------------------------------------------------------- /conda_completions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from typing import List, Tuple, Optional 4 | import subprocess as sp 5 | 6 | import pickle 7 | from pathlib import Path 8 | 9 | import sublime 10 | import sublime_plugin 11 | import threading 12 | 13 | pkgs_fetch_lock = threading.Lock() 14 | 15 | 16 | def run_conda_search() -> List[Tuple[str, str, str, str]]: 17 | p = sp.Popen(['conda', 'search', ], stdout=sp.PIPE, stderr=sp.PIPE) 18 | stdout, stderr = p.communicate() 19 | stdout = stdout.decode() 20 | lines = stdout.split('\n') 21 | for i, line in enumerate(lines): 22 | if line[0] == '#': 23 | break 24 | 25 | return [tuple(line.strip().split()) for line in lines[i+1:] if line.split()] 26 | 27 | 28 | def cache_pkgs_list(pkgs: List[Tuple[str, str, str, str]]) -> None: 29 | cache_dir = Path(sublime.cache_path()) / 'sublime-nextflow' 30 | cache_dir.mkdir(parents=True, exist_ok=True) 31 | cache_path = cache_dir / 'conda_search.pickle' 32 | with open(cache_path, 'wb') as fh: 33 | pickle.dump(pkgs, fh) 34 | 35 | 36 | def fetch_pkgs(window: sublime.Window) -> None: 37 | pkgs = run_conda_search() 38 | cache_pkgs_list(pkgs) 39 | window.status_message(f'Retrieved and cached info for {len(pkgs)} Conda packages') 40 | 41 | 42 | def get_cached_pkgs_list() -> Optional[List[Tuple[str, str, str, str]]]: 43 | cache_dir = Path(sublime.cache_path()) / 'sublime-nextflow' 44 | cache_path = cache_dir / 'conda_search.pickle' 45 | if not cache_path.exists(): 46 | return None 47 | with open(cache_path, 'rb') as fh: 48 | return pickle.load(fh) 49 | 50 | 51 | class NextflowCondaPackagesInfoFetchCommand(sublime_plugin.WindowCommand): 52 | def run(self): 53 | with pkgs_fetch_lock: 54 | self.window.status_message('Fetching Conda package information...') 55 | thread = threading.Thread(target=fetch_pkgs, args=(self.window, )) 56 | thread.daemon = True 57 | thread.start() 58 | 59 | 60 | class NextflowCondaPackagesEventListener(sublime_plugin.EventListener): 61 | def on_query_completions(self, view, prefix, locations): 62 | 63 | if view.syntax().name != 'Nextflow': 64 | return 65 | if len(locations) > 1: 66 | return 67 | point = locations[0] 68 | if not view.score_selector(point, 'source.nextflow meta.definition.process.nextflow meta.definition.conda-directive.nextflow string'): 69 | return 70 | window = view.window() 71 | window.status_message('Getting Conda packages for completions...') 72 | pkgs = get_cached_pkgs_list() 73 | if pkgs: 74 | window.status_message(f'Retrieved {len(pkgs)} Conda packages from cache') 75 | else: 76 | window.status_message('Running nextflow_conda_packages_info_fetch command') 77 | view.run_command('nextflow_conda_packages_info_fetch') 78 | return 79 | pkgs = pkgs[::-1] 80 | flags = sublime.INHIBIT_REORDER | sublime.INHIBIT_WORD_COMPLETIONS 81 | completions = sublime.CompletionList( 82 | completions=[ 83 | sublime.CompletionItem( 84 | trigger=f'{name}={version}={build}' if channel.startswith('pkgs/') else f'{channel}::{name}={version}={build}', 85 | annotation=f'{channel}::{name}={version}={build}', 86 | ) for name,version,build,channel in pkgs 87 | ], 88 | flags=flags 89 | ) 90 | return completions 91 | -------------------------------------------------------------------------------- /container_select.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from typing import List, Tuple, Optional 4 | from datetime import datetime 5 | import re 6 | from urllib.request import urlopen 7 | from urllib.parse import unquote_plus 8 | import pickle 9 | from pathlib import Path 10 | 11 | import sublime 12 | import sublime_plugin 13 | import threading 14 | 15 | container_fetch_lock = threading.Lock() 16 | 17 | 18 | regex_sing_img = re.compile(r'^[^<]+<\/a>\s*(\d{2}-\w{3}-\d{4} \d{2}:\d{2})\s*(\d+)$') 19 | 20 | GALAXY_SINGULARITY_IMAGES_URL = 'https://depot.galaxyproject.org/singularity/' 21 | BASE_QUAYIO_DOCKER_URL = 'quay.io/biocontainers/' 22 | 23 | 24 | def format_size_mb(size: str) -> str: 25 | size_mb = int(size) / (1024**2) 26 | return f'{size_mb:0.1f} MB' 27 | 28 | 29 | def to_isodatetime(s: str, fmt: str = '%d-%b-%Y %H:%M') -> str: 30 | return datetime.strptime(s, fmt).isoformat() 31 | 32 | 33 | def get_images() -> List[Tuple[str, str, str]]: 34 | """Get list of tuples with image name, date modified and size in MB""" 35 | images = [] 36 | print(f'Fetching Biocontainers image info from {GALAXY_SINGULARITY_IMAGES_URL}') 37 | with urlopen(GALAXY_SINGULARITY_IMAGES_URL) as response: 38 | for line in response: 39 | line = line.decode('utf-8').strip() 40 | if not line: 41 | continue 42 | m = regex_sing_img.match(line) 43 | if m: 44 | image_name, dt, size = m.groups() 45 | image_name = unquote_plus(image_name) 46 | isodate = to_isodatetime(dt) 47 | size_mb = format_size_mb(size) 48 | images.append((image_name, isodate, size_mb)) 49 | print(f'Fetched info for {len(images)} Biocontainer images from {GALAXY_SINGULARITY_IMAGES_URL}') 50 | return images 51 | 52 | 53 | def cache_images_list(images: List[Tuple[str, str, str]]) -> None: 54 | cache_dir = Path(sublime.cache_path()) / 'sublime-nextflow' 55 | cache_dir.mkdir(parents=True, exist_ok=True) 56 | cache_path = cache_dir / 'singularity_images.pickle' 57 | with open(cache_path, 'wb') as fh: 58 | pickle.dump(images, fh) 59 | 60 | 61 | def fetch_images(window: sublime.Window) -> None: 62 | images = get_images() 63 | cache_images_list(images) 64 | window.status_message(f'Retrieved and cached info for {len(images)} Biocontainer images from {GALAXY_SINGULARITY_IMAGES_URL}') 65 | 66 | 67 | def get_cached_images_list() -> Optional[List[Tuple[str, str, str]]]: 68 | cache_dir = Path(sublime.cache_path()) / 'sublime-nextflow' 69 | cache_path = cache_dir / 'singularity_images.pickle' 70 | if not cache_path.exists(): 71 | return None 72 | with open(cache_path, 'rb') as fh: 73 | return pickle.load(fh) 74 | 75 | 76 | class NextflowBiocontainerInfoFetchCommand(sublime_plugin.WindowCommand): 77 | def run(self): 78 | with container_fetch_lock: 79 | self.window.status_message('Fetching Biocontainers Docker and Singularity images information...') 80 | thread = threading.Thread(target=fetch_images, args=(self.window, )) 81 | thread.daemon = True 82 | thread.start() 83 | 84 | 85 | class NextflowBiocontainerDirectiveInsertCommand(sublime_plugin.TextCommand): 86 | def run(self, edit, **kwargs): 87 | container = kwargs.get('container', None) 88 | if container: 89 | view = self.view 90 | if len(view.selection) > 1: 91 | return 92 | region = view.selection[0] 93 | point = region.a 94 | row, col = view.rowcol(point) 95 | pad_col = ' ' * col 96 | name = container[0] 97 | text = "if (workflow.containerEngine == 'singularity' && !params.singularity_pull_docker_container) {\n" 98 | text += f"{pad_col} container '{GALAXY_SINGULARITY_IMAGES_URL}{name}'\n" 99 | text += f"{pad_col}" + "} else {\n" 100 | text += f"{pad_col} container '{BASE_QUAYIO_DOCKER_URL}{name}'\n" 101 | text += f"{pad_col}" + "}\n" 102 | view.insert(edit, point, text) 103 | 104 | 105 | class NextflowBiocontainerSelectCommand(sublime_plugin.TextCommand): 106 | def run(self, edit, **kwargs): 107 | view = self.view 108 | if len(view.selection) > 1: 109 | return 110 | region = view.selection[0] 111 | point = region.a 112 | window = view.window() 113 | singularity_images = get_cached_images_list() 114 | if singularity_images: 115 | window.status_message(f'Retrieved {len(singularity_images)} singularity images from cache') 116 | else: 117 | window.status_message(f'Getting singularity images from {GALAXY_SINGULARITY_IMAGES_URL}') 118 | singularity_images = get_images() 119 | window.status_message(f'Retrieved {len(singularity_images)} singularity images') 120 | cache_images_list(singularity_images) 121 | window.status_message(f'Cached info for {len(singularity_images)} singularity images') 122 | 123 | def on_select(x: int): 124 | if x == -1: 125 | return 126 | container = singularity_images[x] 127 | view.run_command('nextflow_biocontainer_directive_insert', dict(container=container)) 128 | 129 | view.window().show_quick_panel([f'{name} ({dt}) [{size_mb}]' for name, dt, size_mb in singularity_images], on_select=on_select) 130 | -------------------------------------------------------------------------------- /images/conda-completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/conda-completion.png -------------------------------------------------------------------------------- /images/container-command-quick-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/container-command-quick-menu.png -------------------------------------------------------------------------------- /images/goto-process-definition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/goto-process-definition.png -------------------------------------------------------------------------------- /images/include-process-command-quick-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/include-process-command-quick-menu.png -------------------------------------------------------------------------------- /images/nfcore-sublime_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/nfcore-sublime_logo.png -------------------------------------------------------------------------------- /images/params-popup-nf-core-viralrecon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/params-popup-nf-core-viralrecon.png -------------------------------------------------------------------------------- /images/process-out-completion-nf-core-viralrecon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/process-out-completion-nf-core-viralrecon.png -------------------------------------------------------------------------------- /images/process-out-popup-nf-core-viralrecon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/process-out-popup-nf-core-viralrecon.png -------------------------------------------------------------------------------- /images/syntax-highlighting-module-imports-nf-core-viralrecon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/syntax-highlighting-module-imports-nf-core-viralrecon.png -------------------------------------------------------------------------------- /images/syntax-highlighting-process-def-nf-core-viralrecon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/syntax-highlighting-process-def-nf-core-viralrecon.png -------------------------------------------------------------------------------- /images/syntax-highlighting-workflow-def-nf-core-viralrecon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterk87/sublime-nextflow/a7a80779fe90ba49957b9c97241be861214be0ff/images/syntax-highlighting-workflow-def-nf-core-viralrecon.png -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "messages/1.0.0.txt", 3 | "install": "messages/install.txt" 4 | } 5 | -------------------------------------------------------------------------------- /messages/1.0.0.txt: -------------------------------------------------------------------------------- 1 | => 1.0.0 2 | 3 | - Added syntax highlight for DSL-2 4 | - Added completions and commands for workflow `params`, `PROCESS.out.`, conda, container, module include 5 | -------------------------------------------------------------------------------- /messages/1.1.0.txt: -------------------------------------------------------------------------------- 1 | => 1.1.0 2 | 3 | Added: 4 | 5 | - more informative popups for showing info about process output so it's easier to select the correct output channel without referencing the process code. 6 | - subworkflow completions and info popups about `take` and `emit` channels 7 | - `conda` directive snippet 8 | 9 | Fixed: 10 | 11 | - comment toggling 12 | -------------------------------------------------------------------------------- /messages/1.2.0.txt: -------------------------------------------------------------------------------- 1 | => 1.2.0 2 | 3 | Added: 4 | 5 | - snippets for process version YML and ch_versions mixing (ver), process modules config (withName) 6 | 7 | Fixed: 8 | 9 | - process popups hanging due to bad regex for getting output emit labels (https://github.com/nf-core/sublime/issues/5) 10 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | ********************************************************************************************* 2 | * sublime-nextflow: Nextflow workflow syntax highlighting and snippets for Sublime Text 4 * 3 | ********************************************************************************************* 4 | 5 | Thanks for using sublime-nextflow! 6 | 7 | If you want more info or have any issues, please see the Github page at: 8 | 9 | https://github.com/nf-core/sublime 10 | -------------------------------------------------------------------------------- /nextflow_include_command.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Iterator 3 | import sys 4 | from pathlib import Path 5 | 6 | import sublime 7 | import sublime_plugin 8 | 9 | 10 | regex_process = re.compile(r'^process +(\w+) *\{\s*$') 11 | regex_functions = re.compile(r'^def +(\w+) *\([^\)]*\) *\{\s*$') 12 | 13 | def find_processes(path: Path) -> Iterator[str]: 14 | with open(path) as f: 15 | for l in f: 16 | m = regex_process.match(l) 17 | if m: 18 | yield m.group(1) 19 | 20 | 21 | def find_functions(path: Path) -> Iterator[str]: 22 | with open(path) as f: 23 | for l in f: 24 | m = regex_functions.match(l) 25 | if m: 26 | yield m.group(1) 27 | 28 | 29 | def relative_path(script_path: Path, import_path: Path) -> str: 30 | for i, parent_path in enumerate(script_path.parents): 31 | try: 32 | if i == 0: 33 | return './' + str(import_path.relative_to(parent_path)) 34 | else: 35 | return '../'*i + str(import_path.relative_to(parent_path)) 36 | except ValueError: 37 | continue 38 | return str(import_path) 39 | 40 | 41 | class NextflowIncludeInsertProcessCommand(sublime_plugin.TextCommand): 42 | def run(self, edit, **kwargs): 43 | process = kwargs.get('process', None) 44 | module_path = kwargs.get('module_path', None) 45 | if process and module_path: 46 | view = self.view 47 | if len(view.selection) > 1: 48 | return 49 | region = view.selection[0] 50 | point = region.a 51 | row, col = view.rowcol(point) 52 | if col != 0: 53 | return 54 | module_path = Path(module_path) 55 | module_path = module_path.parent / module_path.stem 56 | script_path = Path(view.file_name()) 57 | view.insert(edit, point, "include { " + process + " } from '" + relative_path(script_path, module_path) + "' addParams( options: modules['" + process.lower() + "'] )") 58 | 59 | 60 | class NextflowIncludeProcessCommand(sublime_plugin.TextCommand): 61 | def run(self, edit, **kwargs): 62 | view = self.view 63 | if len(view.selection) > 1: 64 | return 65 | region = view.selection[0] 66 | point = region.a 67 | row, col = view.rowcol(point) 68 | if col != 0: 69 | return 70 | folders = view.window().folders() 71 | if not view.file_name(): 72 | return 73 | if not folders: 74 | return 75 | root_dir = Path(folders[0]) 76 | nf_files = [(proc, str(p.absolute())) for p in root_dir.rglob('*.nf') for proc in find_processes(p)] 77 | 78 | def on_select(x: int): 79 | if x == -1: 80 | return 81 | proc, nf_path = nf_files[x] 82 | root = str(root_dir.absolute()) 83 | if nf_path.startswith(root): 84 | view.run_command( 85 | 'nextflow_include_insert_process', 86 | dict(process=proc, 87 | module_path=nf_path 88 | ) 89 | ) 90 | 91 | view.window().show_quick_panel(nf_files, on_select=on_select) 92 | 93 | 94 | class NextflowIncludeInsertFunctionsCommand(sublime_plugin.TextCommand): 95 | def run(self, edit, **kwargs): 96 | funcs = kwargs.get('funcs', None) 97 | module_path = kwargs.get('module_path', None) 98 | if funcs and module_path: 99 | view = self.view 100 | if len(view.selection) > 1: 101 | return 102 | region = view.selection[0] 103 | point = region.a 104 | row, col = view.rowcol(point) 105 | if col != 0: 106 | return 107 | module_path = Path(module_path) 108 | module_path = module_path.parent / module_path.stem 109 | script_path = Path(view.file_name()) 110 | view.insert(edit, point, "include { " + '; '.join(funcs) + " } from '" + relative_path(script_path, module_path) + "'") 111 | 112 | 113 | class NextflowIncludeFunctionsCommand(sublime_plugin.TextCommand): 114 | def run(self, edit, **kwargs): 115 | view = self.view 116 | if len(view.selection) > 1: 117 | return 118 | region = view.selection[0] 119 | point = region.a 120 | row, col = view.rowcol(point) 121 | if col != 0: 122 | return 123 | folders = view.window().folders() 124 | if not view.file_name(): 125 | return 126 | if not folders: 127 | return 128 | root_dir = Path(folders[0]) 129 | nf_files = [] 130 | for p in root_dir.rglob('*.nf'): 131 | abspath = str(p.absolute()) 132 | funcs = list(find_functions(p)) 133 | if funcs: 134 | nf_files.append((funcs, abspath)) 135 | 136 | def on_select(x: int): 137 | if x == -1: 138 | return 139 | funcs, nf_path = nf_files[x] 140 | root = str(root_dir.absolute()) 141 | if nf_path.startswith(root): 142 | view.run_command( 143 | 'nextflow_include_insert_functions', 144 | dict(funcs=funcs, 145 | module_path=nf_path 146 | ) 147 | ) 148 | 149 | view.window().show_quick_panel([('; '.join(x), y) for x,y in nf_files], on_select=on_select) 150 | -------------------------------------------------------------------------------- /params_completions.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Iterator 3 | import sys 4 | from pathlib import Path 5 | import json 6 | 7 | import sublime 8 | import sublime_plugin 9 | 10 | 11 | regex_params = re.compile( 12 | r'\nparams\s*\{\n\s*(.*)', 13 | flags=re.MULTILINE | re.UNICODE | re.DOTALL 14 | ) 15 | regex_param_val = re.compile(r'^(\w+)\s*=\s*(\S+).*$') 16 | 17 | 18 | def params_list(nf_config): 19 | params_match = regex_params.search(nf_config) 20 | if params_match: 21 | brackets = 1 22 | regex_param_val = re.compile(r'^(\w+)\s*=\s*(\S+).*$') 23 | param_val = [] 24 | for l in params_match.group(1).split('\n'): 25 | l = l.strip() 26 | if not l or l.startswith('//'): 27 | continue 28 | m = regex_param_val.match(l) 29 | if m: 30 | param_val.append(m.groups()) 31 | elif l.startswith('}'): 32 | brackets -= 1 33 | else: 34 | print("NOMATCH", l) 35 | if brackets == 0: 36 | break 37 | return param_val 38 | else: 39 | return None 40 | 41 | 42 | def get_param_info(nf_schema: dict, param: str) -> dict: 43 | for defn in nf_schema['definitions'].values(): 44 | try: 45 | return defn['properties'][param] 46 | except KeyError: 47 | continue 48 | return {} 49 | 50 | def format_param_info(param_info: dict) -> str: 51 | param_type = param_info.get('type', 'string') 52 | default = param_info.get('default', '?') 53 | description = param_info.get('description', 'N/A') 54 | out = '' 55 | out += f'

{description}

' 56 | out += ( 57 | f'

Type: {param_type}

' 58 | f'

Default: {default}

' 59 | ) 60 | if 'pattern' in param_info: 61 | out += f'

Pattern: {param_info["pattern"]}

' 62 | if 'enum' in param_info: 63 | enum = param_info['enum'] 64 | if isinstance(enum, list): 65 | enum = ', '.join(sorted(enum)) 66 | out += f'

Enum: {enum}

' 67 | if 'help_text' in param_info: 68 | out += f'

{param_info["help_text"]}

' 69 | return out 70 | 71 | 72 | class NextflowParamsEventListener(sublime_plugin.EventListener): 73 | def on_query_completions(self, view, prefix, locations): 74 | if view.syntax().name != 'Nextflow': 75 | return 76 | if len(locations) > 1: 77 | return 78 | point = locations[0] 79 | if not view.score_selector(point-1, 'source.nextflow punctuation.params.dot'): 80 | return 81 | folders = view.window().folders() 82 | if not folders: 83 | return 84 | root_dir = Path(folders[0]) 85 | nf_config_path = root_dir / 'nextflow.config' 86 | if not nf_config_path.exists(): 87 | print(f'Cannot get params completions. "{nf_config_path}" does not exist!') 88 | return None 89 | 90 | with open(nf_config_path) as f: 91 | nf_config = f.read() 92 | params_values = params_list(nf_config) 93 | if params_values: 94 | flags = sublime.INHIBIT_REORDER | sublime.INHIBIT_WORD_COMPLETIONS 95 | completions = sublime.CompletionList( 96 | completions=[ 97 | sublime.CompletionItem( 98 | trigger=x, 99 | annotation=f'default: {y}', 100 | details=f'nextflow.config: params.{x} = {y}' 101 | ) for x,y in params_values 102 | ], 103 | flags=flags 104 | ) 105 | return completions 106 | 107 | def on_selection_modified_async(self, view): 108 | if view.syntax().name != 'Nextflow': 109 | return 110 | if len(view.selection) > 1: 111 | return 112 | region = view.selection[0] 113 | point = region.a 114 | if not view.score_selector(point, 'source.nextflow entity.name.parameter.nextflow'): 115 | return 116 | folders = view.window().folders() 117 | if not folders: 118 | return 119 | root_dir = Path(folders[0]) 120 | nf_schema_path = root_dir / 'nextflow_schema.json' 121 | if not nf_schema_path.exists(): 122 | return 123 | 124 | scope_region = view.extract_scope(point) 125 | param_text = view.substr(scope_region) 126 | with open(nf_schema_path) as f: 127 | nf_schema = json.load(f) 128 | param_info = get_param_info(nf_schema, param_text) 129 | view.show_popup(format_param_info(param_info)) 130 | -------------------------------------------------------------------------------- /process_label_completion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | from typing import Iterator, Tuple, List 4 | import sys 5 | from pathlib import Path 6 | import json 7 | 8 | import sublime 9 | import sublime_plugin 10 | 11 | regex_withlabel = re.compile(r'\s*withLabel\s*:\s*(?:[\'\"])?(\w+)(?:[\'\"])?\s*\{\s*') 12 | 13 | 14 | def get_config_labels(path: Path) -> List[Tuple[int, str, str]]: 15 | out = [] 16 | text = path.read_text() 17 | for m in regex_withlabel.finditer(text): 18 | start, end = m.span() 19 | bracket_count = 1 20 | end_bracket = -1 21 | for i in range(end+1, len(text)): 22 | c = text[i] 23 | if c == '{': 24 | bracket_count += 1 25 | elif c == '}': 26 | bracket_count -= 1 27 | if bracket_count == 0: 28 | end_bracket = i 29 | break 30 | subtext = '\n'.join(x.strip() for x in text[end:end_bracket].strip().split('\n')) 31 | out.append((path.name, m.group(1), subtext)) 32 | return out 33 | 34 | 35 | class NextflowProcessLabelEventListener(sublime_plugin.ViewEventListener): 36 | def on_query_completions(self, prefix, locations): 37 | view = self.view 38 | if view.syntax().name != 'Nextflow': 39 | return 40 | if len(locations) > 1: 41 | return 42 | point = locations[0] 43 | if not view.score_selector(point, 'source.nextflow meta.definition.process.nextflow'): 44 | return 45 | if not view.substr(view.line(point)).strip().startswith('label'): 46 | return 47 | folders = view.window().folders() 48 | if not folders: 49 | return 50 | root_dir = Path(folders[0]) 51 | labels = [] 52 | for path in root_dir.rglob('**/*.config'): 53 | labels += get_config_labels(path) 54 | if not labels: 55 | return 56 | flags = sublime.INHIBIT_REORDER | sublime.INHIBIT_WORD_COMPLETIONS 57 | completions = sublime.CompletionList( 58 | completions=[ 59 | sublime.CompletionItem( 60 | trigger=f"'{label_name}'", 61 | annotation=f'{config_name}: {label_name}', 62 | details='|'.join(f'{x.replace(" ", "")}' for x in text.split('\n')) 63 | ) for config_name, label_name, text in labels 64 | ], 65 | flags=flags 66 | ) 67 | return completions 68 | -------------------------------------------------------------------------------- /process_popups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | from pathlib import Path 5 | from typing import Tuple, List, Optional 6 | 7 | import sublime 8 | import sublime_plugin 9 | 10 | regex_input_section = re.compile(r'\s*input:\s*') 11 | regex_output_section = re.compile(r'\s*output:\s*') 12 | # regex to find output channels with emit 13 | regex_output_channel = re.compile(r'(.*?),\s*emit:\s*(\w+)', re.DOTALL) 14 | 15 | regex_take_section = re.compile(r'\s*take:\s*') 16 | regex_emit_section = re.compile(r'\s*emit:\s*') 17 | regex_wf_emit = re.compile(r'(\w+)\s*=\s*(.*)') 18 | 19 | 20 | def find_closing_bracket(text: str, start: int) -> int: 21 | count = 1 22 | for i in range(start, len(text)): 23 | c = text[i] 24 | if c == '{': 25 | count += 1 26 | elif c == '}': 27 | count -= 1 28 | if count == 0: 29 | return i 30 | return -1 31 | 32 | 33 | def find_process_name(proc_name: str, text: str) -> int: 34 | m = re.search(r'process\s+' + proc_name + r'\s*{', text) 35 | if m: 36 | return m.end() 37 | return -1 38 | 39 | 40 | def find_workflow_name(proc_name: str, text: str) -> int: 41 | m = re.search(r'workflow\s+' + proc_name + r'\s*{', text) 42 | if m: 43 | return m.end() 44 | return -1 45 | 46 | 47 | def find_proc_input_section(text: str, start: int, end: int) -> int: 48 | m = regex_input_section.search(text[start:end]) 49 | if m: 50 | return m.end() 51 | return -1 52 | 53 | 54 | def find_wf_take_section(text: str, start: int, end: int) -> int: 55 | m = regex_take_section.search(text[start:end]) 56 | if m: 57 | return m.end() 58 | return -1 59 | 60 | 61 | def get_input_channels(text: str, start: int, end: int) -> List[str]: 62 | out = [] 63 | lines = text[start:end].split('\n') 64 | for line in lines: 65 | line = line.strip() 66 | if not line: 67 | continue 68 | if line.startswith('output:') or line.startswith('script:') or line.startswith('when:') or line.startswith( 69 | 'exec:'): 70 | break 71 | out.append(line) 72 | return out 73 | 74 | 75 | def get_wf_take_channels(text: str, start: int, end: int) -> List[str]: 76 | out = [] 77 | lines = text[start:end].split('\n') 78 | for line in lines: 79 | line = line.strip() 80 | if not line: 81 | continue 82 | if line.startswith('main:'): 83 | break 84 | out.append(line) 85 | return out 86 | 87 | 88 | def get_input_channel_text(path: Path, proc_name: str) -> List[str]: 89 | text = path.read_text() 90 | proc_start = find_process_name(proc_name, text) 91 | if proc_start == -1: 92 | return [] 93 | proc_end = find_closing_bracket(text, proc_start) 94 | if proc_end == -1: 95 | return [] 96 | proc_input_section_start = find_proc_input_section(text, proc_start, proc_end) 97 | if proc_input_section_start == -1: 98 | return [] 99 | proc_input_section_start += proc_start 100 | return get_input_channels(text, proc_input_section_start, proc_end) 101 | 102 | 103 | def get_wf_takes(path: Path, proc_name: str) -> List[str]: 104 | text = path.read_text() 105 | wf_start = find_workflow_name(proc_name, text) 106 | if wf_start == -1: 107 | return [] 108 | wf_end = find_closing_bracket(text, wf_start) 109 | if wf_end == -1: 110 | return [] 111 | wf_take_section_start = find_wf_take_section(text, wf_start, wf_end) 112 | if wf_take_section_start == -1: 113 | return [] 114 | wf_take_section_start += wf_start 115 | return get_wf_take_channels(text, wf_take_section_start, wf_end) 116 | 117 | 118 | def proc_input_html(path: str, proc_name: str, input_channels_text: List[str]) -> str: 119 | out = f'

Process: {proc_name}

' 120 | out += f'

File: {path}

' 121 | out += '

Input channels:

' 122 | for x in input_channels_text: 123 | out += f'

{x}

' 124 | return out 125 | 126 | 127 | def find_proc_output_section(text: str, start: int, end: int) -> int: 128 | m = regex_output_section.search(text[start:end]) 129 | if m: 130 | return m.end() 131 | return -1 132 | 133 | 134 | def find_wf_emit_section(text: str, start: int, end: int) -> int: 135 | m = regex_emit_section.search(text[start:end]) 136 | if m: 137 | return m.end() 138 | return -1 139 | 140 | 141 | def get_output_channels(text: str, start: int, end: int) -> List[Tuple[str, str]]: 142 | out = [] 143 | t = text[start:end] 144 | for m in regex_output_channel.finditer(t): 145 | chan, emit = m.groups() 146 | chan = ''.join(x.strip() for x in chan.split('\n')) 147 | out.append((emit, chan)) 148 | return out 149 | 150 | 151 | def get_wf_emit_channels(text: str, start: int, end: int) -> List[Tuple[str, str]]: 152 | out = [] 153 | for m in regex_wf_emit.finditer(text[start:end]): 154 | chan, emit = m.groups() 155 | out.append((chan, emit)) 156 | return out 157 | 158 | 159 | def output_section_lines(text: str, start: int, end: int) -> List[Tuple[int, str]]: 160 | out = [] 161 | lines = text[start:end].split('\n') 162 | for line in lines: 163 | line = line.strip() 164 | if not line: 165 | continue 166 | if line.startswith('script:') or line.startswith('when:') or line.startswith('exec:'): 167 | break 168 | out.append((len(out), line)) 169 | return out 170 | 171 | 172 | def get_output_channel_emits(path: Path, proc_name: str) -> List[Tuple[str, str]]: 173 | text = path.read_text() 174 | proc_start = find_process_name(proc_name, text) 175 | if proc_start == -1: 176 | return [] 177 | proc_end = find_closing_bracket(text, proc_start) 178 | if proc_end == -1: 179 | return [] 180 | proc_output_start = find_proc_output_section(text, proc_start, proc_end) 181 | proc_output_start += proc_start 182 | next_section_match = re.search(r'(when:|exec:|script:|stub:)', text[proc_output_start:]) 183 | if next_section_match: 184 | proc_end = proc_output_start + next_section_match.start() 185 | out = get_output_channels(text, proc_output_start, proc_end) 186 | if not out: 187 | out = output_section_lines(text, proc_output_start, proc_end) 188 | return out 189 | 190 | 191 | def get_wf_emits(path: Path, wf_name: str) -> List[Tuple[str, str]]: 192 | text = path.read_text() 193 | wf_start = find_workflow_name(wf_name, text) 194 | if wf_start == -1: 195 | return [] 196 | wf_end = find_closing_bracket(text, wf_start) 197 | if wf_end == -1: 198 | return [] 199 | wf_emit_start = find_wf_emit_section(text, wf_start, wf_end) 200 | if wf_emit_start == -1: 201 | return [] 202 | wf_emit_start += wf_start 203 | out = get_wf_emit_channels(text, wf_emit_start, wf_end) 204 | if not out: 205 | out = output_section_lines(text, wf_emit_start, wf_end) 206 | return out 207 | 208 | 209 | def proc_output_html(path: str, 210 | proc_or_wf_name: str, 211 | output_channels_text: List[Tuple[str, str, Path]], 212 | focus_channel: str = None, 213 | is_proc: bool = True) -> str: 214 | out = f'

{"Process" if is_proc else "Workflow"}: {proc_or_wf_name}

' 215 | out += f'

File: {path}

' 216 | out += f'

{"Output" if is_proc else "Emit"} channels:

' 217 | for emit, chan in output_channels_text: 218 | out += f'

' 219 | if focus_channel and focus_channel == emit: 220 | out += '' 221 | out += f'{emit}: {chan}' 222 | if focus_channel and focus_channel == emit: 223 | out += '' 224 | out += f'

' 225 | return out 226 | 227 | 228 | def proc_info_html( 229 | path: str, 230 | proc_or_wf_name: str, 231 | input_channels_text: List[str], 232 | output_channels_text: List[Tuple[str, str, Path]], 233 | is_proc: bool = True 234 | ) -> str: 235 | out = f'

{"Process" if is_proc else "Workflow"}: {proc_or_wf_name}

' 236 | out += f'

File: {path}

' 237 | if input_channels_text: 238 | out += f'

{"Input" if is_proc else "Take"} channels:

' 239 | for x in input_channels_text: 240 | out += f'

{x}

' 241 | else: 242 | out += f'No {"input" if is_proc else "take"} channels for {"process" if is_proc else "workflow"}!' 243 | if output_channels_text: 244 | out += f'

{"Output" if is_proc else "Emit"} channels:

' 245 | for emit, chan in output_channels_text: 246 | out += f'

{emit}: {chan}

' 247 | else: 248 | out += f'No {"output" if is_proc else "emit"} channels for {"process" if is_proc else "workflow"}!' 249 | return out 250 | 251 | 252 | def show_proc_info(root_dir: Path, view: sublime.View, point: int) -> None: 253 | proc_name = view.substr(view.word(point)) 254 | m, proc_name = find_import_proc_name(proc_name, view) 255 | path: Optional[Path] = None 256 | input_channels_text = [] 257 | output_channels_text = [] 258 | wfpath = Path(view.file_name()) 259 | is_proc = True 260 | if m: 261 | nf_path = m.group(1) + '.nf' 262 | path = (wfpath.parent / nf_path).resolve() 263 | input_channels_text = get_input_channel_text(path, proc_name) 264 | if not input_channels_text: 265 | input_channels_text = get_wf_takes(path, proc_name) 266 | if input_channels_text: 267 | is_proc = False 268 | else: 269 | view.window().status_message(f'No input/take channels in {proc_name}!') 270 | output_channels_text = get_output_channel_emits(path, proc_name) 271 | if not output_channels_text: 272 | output_channels_text = get_wf_emits(path, proc_name) 273 | print(f'{output_channels_text=}') 274 | if output_channels_text: 275 | is_proc = False 276 | else: 277 | for path in root_dir.rglob('**/*.nf'): 278 | input_channels_text = get_input_channel_text(path, proc_name) 279 | output_channels_text = get_output_channel_emits(path, proc_name) 280 | if input_channels_text or output_channels_text: 281 | break 282 | if not input_channels_text and not output_channels_text: 283 | return 284 | if path is None: 285 | return 286 | short_path = str(path.absolute()).replace(str(root_dir.absolute()) + "/", "") 287 | view.show_popup( 288 | proc_info_html( 289 | path=short_path, 290 | proc_or_wf_name=proc_name, 291 | input_channels_text=input_channels_text, 292 | output_channels_text=output_channels_text, 293 | is_proc=is_proc, 294 | ) 295 | ) 296 | 297 | 298 | def find_import_proc_name(proc_name: str, view: sublime.View) -> Tuple[Optional[re.Match], str]: 299 | region = view.find(r'\w+ +as +' + proc_name, 0) 300 | if region.a != -1 and region.b != -1: 301 | proc_name = view.substr(view.word(sublime.Region(region.a, region.a))) 302 | include_str = view.substr(view.find(r'^include +\{\s*' + proc_name + r"\s*[^}]*\}\s+from\s+'[^']+'", 0)) 303 | print(f'{include_str=}') 304 | m = re.match(r".*from\s+'\./([^']+)'", include_str) 305 | if m is None: 306 | m = re.match(r".*from\s+'([^']+)'", include_str) 307 | return m, proc_name 308 | 309 | 310 | def show_output_channel_popup(root_dir: Path, view: sublime.View, point: int) -> None: 311 | emit_or_out_word_region = view.word(point) 312 | emit_or_out_word_substr = view.substr(emit_or_out_word_region) 313 | focus_channel = None 314 | if emit_or_out_word_substr == 'out': 315 | proc_name = view.substr(view.word(emit_or_out_word_region.a - 2)) 316 | else: 317 | out_word_region = view.word(emit_or_out_word_region.a - 2) 318 | out_word_substr = view.substr(out_word_region) 319 | if out_word_substr != 'out': 320 | print(f'Could not find "out" output channels keyword for process.') 321 | return 322 | focus_channel = emit_or_out_word_substr 323 | proc_name = view.substr(view.word(out_word_region.a - 2)) 324 | window = view.window() 325 | 326 | m, proc_name = find_import_proc_name(proc_name, view) 327 | output_channels_text = [] 328 | path: Optional[Path] = None 329 | wfpath = Path(view.file_name()) 330 | is_proc: bool = True 331 | if m: 332 | nf_path = m.group(1) + '.nf' 333 | path = (wfpath.parent / nf_path).resolve() 334 | if not path.exists(): 335 | paths = list(root_dir.rglob(nf_path)) 336 | if not paths: 337 | view.window().status_message(f'No output channels in {proc_name}!') 338 | path = paths[0] 339 | output_channels_text = get_output_channel_emits(path, proc_name) 340 | if not output_channels_text: 341 | view.window().status_message(f'No output channels in {proc_name}!') 342 | else: 343 | output_channels_text = get_output_channel_emits(path, proc_name) 344 | if not output_channels_text: 345 | output_channels_text = get_wf_emits(path, proc_name) 346 | is_proc = False 347 | if not output_channels_text: 348 | window.status_message(f'No output channels in {proc_name}!') 349 | else: 350 | for path in root_dir.rglob('**/*.nf'): 351 | output_channels_text = get_output_channel_emits(path, proc_name) 352 | if output_channels_text: 353 | break 354 | if not output_channels_text or path is None: 355 | return 356 | short_path = str(path.absolute()).replace(str(root_dir.absolute()) + "/", "") 357 | view.show_popup(proc_output_html(short_path, proc_name, output_channels_text, focus_channel, is_proc)) 358 | 359 | 360 | class NextflowWorkflowProcessCallEventListener(sublime_plugin.EventListener): 361 | def on_selection_modified_async(self, view: sublime.View): 362 | """Show popups for process calls and output channel property access 363 | 364 | When the cursor is navigated over: 365 | 366 | - a process name (entity.name.class.process.nextflow) 367 | - process out keyword (e.g. FASTQC.out) (keyword.process.out.nextflow) 368 | - named output channel (e.g. FASTQC.out.html) (variable.channel.process-output-emit.nextflow) 369 | 370 | A popup will be shown with info about the process input/output channels. For named output channels, the accessed output channel will be highlighted. 371 | """ 372 | if view.syntax().name != 'Nextflow': 373 | return 374 | if len(view.selection) > 1: 375 | return 376 | folders = view.window().folders() 377 | if not folders: 378 | return 379 | root_dir = Path(folders[0]) 380 | region = view.selection[0] 381 | point = region.a 382 | point_before = point - 1 383 | # if cursor on process name, then show popup with input/output channel info 384 | if view.score_selector(point, 385 | 'source.nextflow meta.definition.workflow.nextflow entity.name.class.process.nextflow'): 386 | show_proc_info(root_dir, view, point) 387 | return 388 | out_chan_scope = '(variable.channel.process-output-emit.nextflow | keyword.process.out.nextflow)' 389 | if view.score_selector(point, out_chan_scope) or view.score_selector(point - 1, out_chan_scope): 390 | show_output_channel_popup(root_dir, view, point) 391 | return 392 | # if cursor on or after `out` or named output channel, then show popup with process output channel info 393 | # highlighting named process 394 | proc_call_input_scope = 'source.nextflow meta.definition.workflow.nextflow meta.process-call.nextflow - (' \ 395 | 'punctuation.accessor.dot.process-out.nextflow | entity | keyword | variable) ' 396 | if not view.score_selector(point, proc_call_input_scope): 397 | return 398 | if not view.score_selector(point_before, proc_call_input_scope): 399 | return 400 | s = view.substr(point_before) 401 | if s not in (' ', ',', '('): 402 | return 403 | regions = [x for x in view.find_by_selector('meta.process-call.nextflow') if x.contains(point)] 404 | if not regions: 405 | return 406 | proc_call_str = view.substr(regions[0]) 407 | m = re.match(r'^(\w+)', proc_call_str) 408 | if not m: 409 | return 410 | proc_name = m.group(1) 411 | m, proc_name = find_import_proc_name(proc_name, view) 412 | input_channels_text = [] 413 | path: Optional[Path] = None 414 | wfpath = Path(view.file_name()) 415 | if m: 416 | nf_path = m.group(1) + '.nf' 417 | path = (wfpath.parent / nf_path).resolve() 418 | if not path.exists(): 419 | print(f'{path=} does not exist') 420 | paths = list(root_dir.rglob(nf_path)) 421 | if len(paths) == 0: 422 | view.window().status_message(f'No input channels in {proc_name}!') 423 | path = paths[0] 424 | input_channels_text = get_input_channel_text(path, proc_name) 425 | if not input_channels_text: 426 | view.window().status_message(f'No input channels in {proc_name}!') 427 | else: 428 | input_channels_text = get_input_channel_text(path, proc_name) 429 | if not input_channels_text: 430 | view.window().status_message(f'No input channels in {proc_name}!') 431 | else: 432 | for path in root_dir.rglob('**/*.nf'): 433 | input_channels_text = get_input_channel_text(path, proc_name) 434 | if input_channels_text: 435 | break 436 | if not input_channels_text: 437 | return 438 | if path is None: 439 | return 440 | short_path = str(path.absolute()).replace(str(root_dir.absolute()), "") 441 | view.show_popup(proc_input_html(short_path, proc_name, input_channels_text)) 442 | 443 | def on_query_completions(self, view, prefix, locations): 444 | window = view.window() 445 | if view.syntax().name != 'Nextflow': 446 | return 447 | if len(locations) > 1: 448 | return 449 | point = locations[0] 450 | if not view.score_selector(point - 1, 451 | 'source.nextflow meta.definition.workflow.nextflow punctuation.accessor.dot.process-out.nextflow'): 452 | return 453 | out_word_region = view.word(point - 2) 454 | if view.substr(out_word_region) != 'out': 455 | return 456 | proc_name_region = view.word(out_word_region.a - 1) 457 | proc_name = view.substr(proc_name_region) 458 | if not proc_name: 459 | return 460 | 461 | folders = view.window().folders() 462 | if not folders: 463 | return 464 | root_dir = Path(folders[0]) 465 | 466 | m, proc_name = find_import_proc_name(proc_name, view) 467 | emits = [] 468 | path = None 469 | wfpath = Path(view.file_name()) 470 | if m: 471 | nf_path = m.group(1) + '.nf' 472 | path = (wfpath.parent / nf_path).resolve() 473 | if path.exists(): 474 | emits = get_output_channel_emits(path, proc_name) 475 | if not emits: 476 | emits = get_wf_emits(path, proc_name) 477 | if not emits: 478 | window.status_message(f'No named output channels in {proc_name}!') 479 | else: 480 | for path in root_dir.rglob('**/*.nf'): 481 | emits = get_output_channel_emits(path, proc_name) 482 | if emits: 483 | break 484 | if not emits: 485 | return 486 | if path is None: 487 | return 488 | flags = sublime.INHIBIT_REORDER | sublime.INHIBIT_WORD_COMPLETIONS 489 | return sublime.CompletionList( 490 | completions=[ 491 | sublime.CompletionItem( 492 | trigger=emit, 493 | annotation=chan, 494 | details=f'{chan}

From "{str(path.absolute()).replace(str(root_dir.absolute()) + "/", "")}"

', 495 | ) 496 | for emit, chan in emits 497 | ], 498 | flags=flags, 499 | ) 500 | -------------------------------------------------------------------------------- /snippets/nextflow-ch_versions-mix.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | ver 6 | source.nextflow meta.definition.workflow.nextflow 7 | 8 | -------------------------------------------------------------------------------- /snippets/nextflow-conda.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | conda 6 | source.nextflow meta.definition.process.nextflow 7 | 8 | -------------------------------------------------------------------------------- /snippets/nextflow-done-log-oncomplete-onerror.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 20 | done 21 | source.nextflow - meta 22 | 23 | -------------------------------------------------------------------------------- /snippets/nextflow-dsl2.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | dsl2 6 | source.nextflow - meta 7 | 8 | -------------------------------------------------------------------------------- /snippets/nextflow-env.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | !env 6 | source.nextflow - meta 7 | 8 | -------------------------------------------------------------------------------- /snippets/nextflow-log-info.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 7 | info 8 | source.nextflow 9 | 10 | -------------------------------------------------------------------------------- /snippets/nextflow-modules-config.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | filename.equals('versions.yml') ? null : filename } 10 | ] 11 | ] 12 | } 13 | $0 14 | ]]> 15 | withName 16 | source.nextflow meta.block.nextflow 17 | 18 | -------------------------------------------------------------------------------- /snippets/nextflow-paired-end-illumina-reads-input-channel.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 8 | illumina 9 | source.nextflow - meta.definition.process.nextflow 10 | 11 | -------------------------------------------------------------------------------- /snippets/nextflow-process.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | \$output 13 | """ 14 | } 15 | ]]> 16 | proc 17 | source.nextflow - meta 18 | 19 | -------------------------------------------------------------------------------- /snippets/nextflow-publishDir.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 6 | pub 7 | source.nextflow meta.definition.process.nextflow 8 | 9 | -------------------------------------------------------------------------------- /snippets/nextflow-script-cpus.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | cpus 6 | source.nextflow meta.definition.process.nextflow string.quoted.double.block.nextflow 7 | 8 | -------------------------------------------------------------------------------- /snippets/nextflow-script-memory-gigabytes.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | memg 6 | source.nextflow meta.definition.process.nextflow string.quoted.double.block.nextflow 7 | 8 | -------------------------------------------------------------------------------- /snippets/nextflow-script-memory-megabytes.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | memm 6 | source.nextflow meta.definition.process.nextflow string.quoted.double.block.nextflow 7 | 8 | -------------------------------------------------------------------------------- /snippets/nextflow-tag.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | tag 6 | source.nextflow meta.definition.process.nextflow 7 | 8 | -------------------------------------------------------------------------------- /snippets/nextflow-versions-yml.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | versions.yml 4 | "\${task.process}": 5 | $1: \\\$($1 --version | sed 's/$1 //')$0 6 | END_VERSIONS 7 | ]]> 8 | ver 9 | source.nextflow meta.definition.process.nextflow string.quoted.double.block.nextflow 10 | 11 | -------------------------------------------------------------------------------- /syntax_test_.Nextflow: -------------------------------------------------------------------------------- 1 | // SYNTAX TEST "Packages/sublime-nextflow/Nextflow.sublime-syntax" 2 | #!/usr/bin/env nextflow 3 | // <- comment.line.hashbang.nextflow 4 | 5 | // <- source.nextflow 6 | 7 | include { SPADES as spades; IQTREE } from './modules/local/spades' 8 | // ^ meta.import-module.nextflow keyword.control.include-module.nextflow 9 | // ^ meta.import-module.nextflow meta.module-include-block.nextflow punctuation.section.module-include-block.begin.nextflow 10 | // ^ meta.import-module.nextflow meta.module-include-block.nextflow keyword.operator.as.nextflow 11 | // ^ meta.import-module.nextflow meta.module-include-block.nextflow punctuation.separator.include-module-list.nextflow 12 | // ^ meta.import-module.nextflow keyword.control.include-module.from.nextflow 13 | // ^ meta.import-module.nextflow string.quoted.single.relative-import-path.nextflow punctuation.definition.string.begin.nextflow 14 | // ^ string.quoted.single.relative-import-path.nextflow punctuation.definition.relative-import-path.nextflow 15 | // ^ meta.import-module.nextflow string.quoted.single.relative-import-path.nextflow punctuation.definition.string.end.nextflow 16 | include { SPADES as spades; IQTREE } from '/modules/local/spades' 17 | // ^ invalid.illegal.relative-import-path.nextflow 18 | 19 | include { SPADES 20 | // ^ meta.import-module.nextflow meta.module-include-block.nextflow invalid.illegal.unclosed-include-body.nextflow 21 | 22 | include { IQTREE } from '../modules/local/iqtree' addParams( options: modules['iqtree'] ) 23 | 24 | params.reads_dir = "$baseDir/data/reads/" 25 | // ^ support.variable.params.nextflow 26 | // ^ entity.name.parameter.nextflow 27 | 28 | params.cdhit_identity_threshold = 0.9 29 | // ^ keyword.operator.assignment.nextflow 30 | // ^ constant.numeric.nextflow 31 | 32 | // <- source.nextflow 33 | 34 | // Show help message if --help specified 35 | // ^ source.nextflow comment.line.double-slash.nextflow 36 | if (params.help){ 37 | // <- keyword.control.nextflow 38 | // ^ support.variable.params.nextflow 39 | // ^ entity.name.parameter.nextflow 40 | helpMessage() 41 | // ^ meta.block.nextflow meta.method-call.nextflow meta.method.nextflow 42 | exit 0 43 | // ^ meta.block.nextflow support.function.exit.nextflow 44 | // ^ meta.block.nextflow constant.numeric.nextflow 45 | } 46 | 47 | Channel 48 | // <- storage.type.class.nextflow 49 | .fromFilePairs(params.input) 50 | // ^ support.function.channel-factory.nextflow 51 | .map { [ it[0].replaceAll(/_S\d{1,2}_L001/, ""), it[1] ] } 52 | // ^ keyword.operator.channel-transforming-operator.nextflow 53 | // ^ meta.block.nextflow meta.structure.nextflow meta.structure.nextflow constant.numeric.nextflow 54 | // ^ meta.block.nextflow meta.structure.nextflow meta.method-call.nextflow meta.method.nextflow 55 | // ^ variable.language.nextflow 56 | // ^ meta.block.nextflow meta.structure.nextflow meta.method-call.nextflow string.regexp.nextflow 57 | .set { ch_input } 58 | // ^ keyword.operator.channel-other-operator.nextflow 59 | 60 | // <- source.nextflow 61 | 62 | log.info """ 63 | // <- support.variable.log.nextflow 64 | ${workflow.commandLine} 65 | // ^ source.nextflow.embedded.source support.variable.workflow.nextflow 66 | """ 67 | 68 | // <- source.nextflow 69 | 70 | process download_phix { 71 | // ^ meta.definition.process.nextflow storage.type.return-type.def.nextflow 72 | // ^ meta.definition.process.nextflow entity.name.process.nextflow 73 | // ^ meta.definition.process.nextflow punctuation.definition.process.begin.nextflow 74 | output: 75 | file('phix.fa') into phix_file 76 | // ^ meta.definition.process.nextflow meta.definition.process-output.nextflow support.function.file.nextflow 77 | // ^ meta.definition.process-output.nextflow string.quoted.single.nextflow 78 | // ^ meta.definition.process-output.nextflow keyword.operator.into.nextflow 79 | 80 | ''' 81 | // ^ meta.definition.process.nextflow string.quoted.single.block.nextflow punctuation.definition.string.begin.nextflow 82 | curl "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=nuccore&id=NC_001422.1&rettype=fasta&retmode=text" > phix.fa 83 | // ^ meta.definition.process.nextflow string.quoted.single.block.nextflow 84 | ''' 85 | // ^ meta.definition.process.nextflow string.quoted.single.block.nextflow punctuation.definition.string.end.nextflow 86 | } 87 | 88 | // <- source.nextflow 89 | 90 | process remove_phix { 91 | // <- meta.definition.process.nextflow 92 | // <- storage.type.return-type.def.nextflow 93 | // ^ entity.name.process.nextflow 94 | // ^ meta.definition.process.nextflow punctuation.definition.process.begin.nextflow 95 | tag "$sample_id" 96 | // ^ meta.definition.process.nextflow support.type.nextflow 97 | // ^ string.quoted.double.nextflow variable.other.interpolated.nextflow 98 | 99 | input: 100 | // ^ meta.definition.process.nextflow meta.definition.process-input.nextflow keyword.other.process-input.nextflow 101 | file phix from phix_file 102 | // ^ meta.definition.process-input.nextflow support.function.file.nextflow 103 | // ^ meta.definition.process-input.nextflow keyword.operator.from.nextflow 104 | set sample_id, file(read1), file(read2) from samples_ch 105 | // ^ meta.definition.process-input.nextflow keyword.other.channel.set.nextflow 106 | // ^ meta.definition.process-input.nextflow support.function.file.nextflow 107 | // ^ meta.definition.process-input.nextflow keyword.operator.from.nextflow 108 | output: 109 | // ^ meta.definition.process.nextflow meta.definition.process-output.nextflow 110 | set val(sample_id), file(filt_reads1), file(filt_reads2) into filtered_reads_ch, filtered_reads_ch2 111 | // ^ meta.definition.process-output.nextflow keyword.other.channel.set.nextflow 112 | // ^ meta.definition.process-output.nextflow support.function.val.nextflow 113 | // ^ meta.definition.process-output.nextflow support.function.file.nextflow 114 | // ^ meta.definition.process-output.nextflow keyword.operator.into.nextflow 115 | file stats into remove_phix_stats_ch 116 | 117 | script: 118 | // ^ meta.definition.process.nextflow keyword.other.process-script-def.nextflow 119 | filt_reads1 = "${sample_id}_1.fq" 120 | filt_reads2 = "${sample_id}_2.fq" 121 | stats = "${sample_id}-remove_phix-stats.txt" 122 | """ 123 | // ^ meta.definition.process.nextflow string.quoted.double.block.nextflow punctuation.definition.string.begin.nextflow 124 | bbduk.sh in1=${read1} in2=${read2} out1=${filt_reads1} out2=${filt_reads2} ref=$phix k=31 hdist=1 stats=${stats} 125 | // ^ meta.definition.process.nextflow string.quoted.double.block.nextflow 126 | // ^ string.quoted.double.block.nextflow source.nextflow.embedded.source 127 | // ^ string.quoted.double.block.nextflow variable.other.interpolated.nextflow 128 | """ 129 | // ^ meta.definition.process.nextflow string.quoted.double.block.nextflow punctuation.definition.string.end.nextflow 130 | } 131 | 132 | process BLAST { 133 | conda 'bioconda::blast=2.11.0=pl526he19e7b1_0' 134 | // ^ meta.definition.conda-directive.nextflow support.type.conda.nextflow 135 | // ^ meta.definition.conda-directive.nextflow string.quoted.single.nextflow punctuation.definition.string.begin.nextflow 136 | 137 | input: 138 | tuple val(meta), path(fasta) 139 | // ^ meta.definition.process.nextflow meta.definition.process-input.nextflow keyword.other.channel.tuple.nextflow 140 | // ^ meta.definition.process-input.nextflow support.function.val.nextflow 141 | // ^ meta.definition.process-input.nextflow support.function.path.nextflow 142 | path(db) 143 | // ^ meta.definition.process-input.nextflow support.function.path.nextflow 144 | 145 | output: 146 | tuple val(meta), path("*.blast.txt"), optional: true, emit: txt 147 | // ^ meta.definition.process.nextflow meta.definition.process-output.nextflow keyword.other.channel.tuple.nextflow 148 | // ^ meta.definition.process-output.nextflow support.function.val.nextflow 149 | // ^ meta.definition.process-output.nextflow support.function.path.nextflow 150 | path('*.version.txt'), emit: version 151 | path('file') 152 | 153 | script: 154 | """ 155 | blastn -db $db -query $fasta -outfmt 6 -out ${meta.id}.blast.txt 156 | 157 | blastn -version > blast.version.txt 158 | """ 159 | } 160 | 161 | 162 | // <- source.nextflow 163 | 164 | process MASH_SCREEN { 165 | tag "${meta.id}" 166 | publishDir "$outdir/mash_screen", mode: params.publish_dir_mode 167 | // ^ meta.definition.process.nextflow support.type.nextflow 168 | // ^ meta.definition.process.nextflow constant.other.key.nextflow 169 | 170 | input: 171 | path db 172 | // ^ meta.definition.process.nextflow meta.definition.process-input.nextflow meta.function-call.process-path.nextflow variable.other.process.nextflow 173 | tuple val(meta), path(reads) 174 | 175 | output: 176 | tuple val(meta), path('*.mash_screen.tsv'), emit: results 177 | // ^ meta.definition.process-output.nextflow meta.definition.process-output-channel.nextflow meta.function-call.process-val.nextflow support.function.val.nextflow 178 | // ^ meta.definition.process-output.nextflow meta.definition.process-output-channel.nextflow meta.function-call.process-val.nextflow variable.other.process.nextflow 179 | path '*.version.txt', emit: version 180 | 181 | """ 182 | echo "identity\tmatching_sketches\tmedian_multiplicity\tp_value\tquery_id\tquery_comment" > ${meta.id}.mash_screen.tsv 183 | mash screen -w -p ${task.cpus} $db $reads | sort -grk1 - >> ${meta.id}.mash_screen.tsv 184 | 185 | mash --version > mash.version.txt 186 | """ 187 | } 188 | 189 | process ProcessName { 190 | tag "info about process" 191 | 192 | input: 193 | set val(value), file(file1) into ch_files 194 | // ^ source.nextflow meta.definition.process.nextflow meta.definition.process-input.nextflow invalid.illegal.into.nextflow 195 | 196 | output: 197 | set val(value), file(output) from ch_output 198 | // ^ source.nextflow meta.definition.process.nextflow meta.definition.process-output.nextflow invalid.illegal.from.nextflow 199 | 200 | script: 201 | """ 202 | dostuff 203 | """ 204 | } 205 | 206 | 207 | Channel.empty() // Create assemblies channel 208 | // ^ support.function.channel-factory.nextflow 209 | // ^ comment.line.double-slash.nextflow 210 | .mix(shovill_assembly_ch, unicycler_assembly_ch) 211 | // ^ keyword.operator.channel-combining-operator.nextflow 212 | .set { assembly_ch } 213 | // ^ keyword.operator.channel-other-operator.nextflow 214 | 215 | // <- source.nextflow 216 | 217 | 218 | workflow { 219 | // ^ meta.definition.workflow.nextflow keyword.declaration.workflow.nextflow 220 | ch_input = Channel.fromPath(params.input) 221 | SPADES(ch_input, params.spades_args) 222 | // ^ meta.process-call.nextflow entity.name.class.process.nextflow 223 | // ^ meta.process-call.nextflow punctuation.section.process-call.begin.nextflow 224 | // ^ meta.process-call.nextflow punctuation.section.arguments.end.nextflow 225 | // ^ source.nextflow meta.definition.workflow.nextflow meta.process-call.nextflow variable.parameter.channel.nextflow 226 | ch_db = Channel.fromPath(params.blast_db) 227 | BLAST(SPADES.out.txt, ch_db) 228 | // ^ meta.process-call.nextflow punctuation.section.process-call.begin.nextflow 229 | // ^ meta.process-call.nextflow variable.channel.process-output-emit.nextflow 230 | if (!params.skip_kraken2) { 231 | ch_index = Channel.fromPath(params.kraken2_index) 232 | KRAKEN2(ch_index, ch_input) 233 | // ^ meta.conditional-block.nextflow meta.process-call.nextflow entity.name.class.process.nextflow 234 | } else { 235 | ch_index = Channel.fromPath(params.kraken2_index) 236 | KRAKEN2(ch_index, ch_input) 237 | // ^ meta.conditional-block.nextflow meta.process-call.nextflow entity.name.class.process.nextflow 238 | } 239 | WHATEVER(ch_input) 240 | // ^ meta.process-call.nextflow entity.name.class.process.nextflow 241 | } 242 | 243 | workflow DOSTUFF { 244 | // ^ meta.definition.workflow.nextflow entity.name.workflow.nextflow 245 | } 246 | 247 | 248 | workflow.onComplete { 249 | // ^ support.variable.workflow.nextflow 250 | println """ 251 | // ^ meta.block.nextflow support.function.print.nextflow 252 | Completed at : ${workflow.complete} 253 | // ^ meta.block.nextflow string.quoted.double.block.nextflow source.nextflow.embedded.source support.variable.workflow.nextflow 254 | Results Dir : ${file(params.outdir)} 255 | // ^ source.nextflow.embedded.source support.function.file.nextflow 256 | // ^ support.variable.params.nextflow 257 | // ^ entity.name.parameter.nextflow 258 | """.stripIndent() 259 | // ^ meta.method-call.nextflow meta.method.nextflow 260 | } 261 | --------------------------------------------------------------------------------