### Terminalix plugin forked from awesome Atom-terminal-panel Copyright by isis97/VadimDor MIT licensed The main terminal view class, which does the most of all the work. ### #coffeelint: disable=max_line_length lastOpenedView = null fs = include 'fs' os = include 'os' {$, TextEditorView, View} = include 'atom-space-pen-views' {spawn, exec, execSync} = include 'child_process' {execR, execSyncR} = include 'ssh2-exec' {connR} = include 'ssh2-connect' EventEmitter = (include 'events').EventEmitter {resolve, dirname, extname, sep} = include 'path' findConfig = include 'find-config' yaml = include 'js-yaml' connect = include 'ssh2-connect' exec_SSH = include 'ssh2-exec' execSSH = include 'ssh-exec-plus' ansihtml = include 'ansi-html-stream' stream = include 'stream' iconv = include 'iconv-lite' ATPCommandFinderView = include 'atp-command-finder' ATPCore = include 'atp-core' ATPCommandsBuiltins = include 'atp-builtins-commands' ATPVariablesBuiltins = include 'atp-builtins-variables' window.$ = window.jQuery = $ include 'jquery-autocomplete-js' module.exports = class ATPOutputView extends View cwd: null #streamsEncoding: 'iso-8859-3' streamsEncoding : '"'+atom.config.get('terminalix.textEncode')+'"' _cmdintdel: 50 echoOn: true configFileFtp: null redirectOutput: '' specsMode: false inputLine: 0 helloMessageShown: false minHeight: 250 util: include 'atp-terminal-util' currentInputBox: null #currentInputBox: null currentInputBoxTmr: null volatileSuggestions: [] disposables: dispose: (field) => if not this[field]? this[field] = [] a = this[field] for i in [0..a.length-1] by 1 a[i].dispose() add: (field, value) => if not this[field]? this[field] = [] this[field].push value keyCodes: { enter: 13 arrowUp: 38 arrowDown: 40 arrowLeft: 37 arrowRight: 39 } localCommandAtomBindings: [] localCommands: ATPCommandsBuiltins @content: -> @div tabIndex: -1, class: 'panel atp-panel panel-bottom', outlet: 'atpView', => @div class: 'terminal panel-divider', style: 'cursor:n-resize;width:100%;height:8px;', outlet: 'panelDivider' @button outlet: 'maximizeIconBtn', class: 'atp-maximize-btn', click: 'maximize' @button outlet: 'closeIconBtn', class: 'atp-close-btn', click: 'close' @button outlet: 'destroyIconBtn', class: 'atp-destroy-btn', click: 'destroy' @div class: 'panel-heading btn-toolbar', outlet:'consoleToolbarHeading', => @div class: 'btn-group', outlet:'consoleToolbar', => @button outlet: 'killBtn', click: 'kill', class: 'btn hide', => @span 'kill' @button outlet: 'exitBtn', click: 'destroy', class: 'btn', => @span 'exit' @button outlet: 'closeBtn', click: 'close', class: 'btn', => @span class: "icon icon-x" @span 'close' @button outlet: 'openConfigBtn', class: 'btn icon icon-gear inline-block-tight button-settings', click: 'showSettings', => @span 'Open config' @button outlet: 'reloadConfigBtn', class: 'btn icon icon-gear inline-block-tight button-settings', click: 'reloadSettings', => @span 'Reload config' @div class: 'atp-panel-body', => @pre class: "terminal", outlet: "cliOutput" toggleAutoCompletion: () -> if @currentInputBoxCmp? @currentInputBoxCmp.enable() @currentInputBoxCmp.repaint() @currentInputBoxCmp.showDropDown() @currentInputBox.find('.terminal-input').height('100px') fsSpy: () -> @volatileSuggestions = [] if @cwd? fs.readdir @cwd, (err, files) => if files? for file in files @volatileSuggestions.push file turnSpecsMode: (state) -> @specsMode = state getRawOutput: () -> t = @getHtmlOutput().replace(/<[^>]*>/igm, "") t = @util.replaceAll ">", ">", t t = @util.replaceAll "<", "<", t t = @util.replaceAll """, "\"", t return t getHtmlOutput: () -> return @cliOutput.html() resolvePath: (path) -> path = @util.replaceAll '\"', '', path filepath = '' if path.match(/([A-Za-z]):/ig) != null filepath = path else filepath = @getCwd() + '/' + path filepath = @util.replaceAll '\\', '/', filepath return @util.replaceAll '\\', '/', (resolve filepath) reloadSettings: () -> @onCommand 'update' showSettings: () -> setTimeout () -> panelPath = atom.packages.resolvePackagePath 'terminalix' atomPath = resolve panelPath+'/../..' configPath = atomPath + '/terminal-commands.json' atom.workspace.open configPath , 50 focusInputBox: () -> if @currentInputBoxCmp? @currentInputBoxCmp.input.focus() updateInputCursor: (textarea) -> @rawMessage 'test\n' val = textarea.val() textarea .blur() .focus() .val("") .val(val) removeInputBox: () -> @cliOutput.find('.atp-dynamic-input-box').remove() putInputBox: () -> if @currentInputBoxTmr? clearInterval @currentInputBoxTmr @currentInputBoxTmr = null @cliOutput.find('.atp-dynamic-input-box').remove() prompt = @getCommandPrompt('') @currentInputBox = $( '
' + '
' + '
' ) @currentInputBox.prepend '  ' @currentInputBox.prepend prompt #@cliOutput.mousedown (e) => # if e.which is 1 # @focusInputBox() history = [] if @currentInputBoxCmp? history = @currentInputBoxCmp.getInputHistory() inputComp = @currentInputBox.find '.terminal-input' @currentInputBoxCmp = inputComp.autocomplete { animation: [ ['opacity', 0, 0.8] ] isDisabled: true inputHistory: history inputWidth: '80%' dropDownWidth: '30%' dropDownDescriptionBoxWidth: '30%' dropDownPosition: 'top' showDropDown: atom.config.get 'terminalix.enableConsoleSuggestionsDropdown' } @currentInputBoxCmp .confirmed(() => @currentInputBoxCmp.disable().repaint() @onCommand() ).changed((inst, text) => if inst.getText().length <= 0 @currentInputBoxCmp.disable().repaint() @currentInputBox.find('.terminal-input').height('20px') ) @currentInputBoxCmp.input.keydown((e) => if (e.keyCode == 17) and (@currentInputBoxCmp.getText().length > 0) ### @currentInputBoxCmp.enable().repaint() @currentInputBoxCmp.showDropDown() @currentInputBox.find('.terminal-input').height('100px'); ### else if (e.keyCode == 32) or (e.keyCode == 8) @currentInputBoxCmp.disable().repaint() @currentInputBox.find('.terminal-input').height('20px') ) endsWith = (text, suffix) -> return text.indexOf(suffix, text.length - suffix.length) != -1 @currentInputBoxCmp.options = (instance, text, lastToken) => token = lastToken if not token? token = '' if not (endsWith(token, '/') or endsWith(token, '\\')) token = @util.replaceAll '\\', sep, token token = token.split sep token.pop() token = token.join(sep) if not endsWith(token, sep) token = token + sep o = @getCommandsNames().concat(@volatileSuggestions) fsStat = [] if token? try fsStat = fs.readdirSync(token) for i in [0..fsStat.length-1] by 1 fsStat[i] = token + fsStat[i] catch e ret = o.concat(fsStat) return ret @currentInputBoxCmp.hideDropDown() setTimeout () => @currentInputBoxCmp.input.focus() , 0 @cliOutput.append @currentInputBox inputBoxState: () -> inputState = @cliOutput.find('.atp-dynamic-input-box').clone() inputState.find('.autocomplete-wrapper').replaceWith () -> input = $(this).find('.autocomplete-input')[0] return $('', class: input.className, text: input.value) inputState.removeClass 'atp-dynamic-input-box' return inputState.prop('outerHTML') + '\n' readInputBox: () -> ret = '' if @currentInputBoxCmp? # ret = @currentInputBox.find('.terminal-input').val() ret = @currentInputBoxCmp.getText() return ret requireCSS: (location) -> if not location? return location = resolve location console.log ("Require terminalix plugin CSS file: "+location+"\n") if atom.config.get('terminalix.logConsole') or @specsMode $('head').append "" resolvePluginDependencies: (path, plugin) -> config = plugin.dependencies if not config? return css_dependencies = config.css if not css_dependencies? css_dependencies = [] for css_dependency in css_dependencies @requireCSS path+"/"+css_dependency delete plugin['dependencies'] init: () -> ### TODO: test-autocomplete Remove this! el = $('
') el.autocomplete({ inputWidth: '80%' }) $('body').append(el) ### @streamsEncoding = '"'+atom.config.get('terminalix.textEncode')+'"' lastY = -1 mouseDown = false panelDraggingActive = false @panelDivider .mousedown () -> panelDraggingActive = true .mouseup () -> panelDraggingActive = false $(document) .mousedown () -> mouseDown = true .mouseup () -> mouseDown = false .mousemove (e) -> if mouseDown and panelDraggingActive if lastY != -1 delta = e.pageY - lastY @cliOutput.height @cliOutput.height()-delta lastY = e.pageY else lastY = -1 normalizedPath = require("path").join(__dirname, "../commands") console.log ("Loading terminalix plugins from the directory: "+normalizedPath+"\n") if atom.config.get('terminalix.logConsole') or @specsMode fs.readdirSync(normalizedPath).forEach( (folder) => fullpath = resolve "../commands/" +folder console.log ("Require terminalix plugin: "+folder+"\n") if atom.config.get('terminalix.logConsole') or @specsMode obj = require ("../commands/" +folder+"/index.coffee") console.log "Plugin loaded." if atom.config.get('terminalix.logConsole') @resolvePluginDependencies fullpath, obj for key, value of obj if value.command? @localCommands[key] = value @localCommands[key].source = 'external-functional' @localCommands[key].sourcefile = folder else if value.variable? value.name = key ATPVariablesBuiltins.putVariable value ) console.log ("All plugins were loaded.") if atom.config.get('terminalix.logConsole') if ATPCore.getConfig()? actions = ATPCore.getConfig().actions if actions? for action in actions if action.length > 1 obj = {} obj['terminalix:'+action[0]] = () => @open() @onCommand action[1] atom.commands.add 'atom-workspace', obj if atom.workspace? eleqr = atom.workspace.getActivePaneItem() ? atom.workspace eleqr = atom.views.getView(eleqr) atomCommands = atom.commands.findCommands({target: eleqr}) for command in atomCommands comName = command.name com = {} com.description = command.displayName com.command = ((comNameP) -> return (state, args) -> ele = atom.workspace.getActivePaneItem() ? atom.workspace ele = atom.views.getView(ele) atom.commands.dispatch ele, comNameP return (state.consoleLabel 'info', "info") + (state.consoleText 'info', 'Atom command executed: '+comNameP) )(comName) com.source = "internal-atom" @localCommands[comName] = com toolbar = ATPCore.getConfig().toolbar if toolbar? toolbar.reverse() for com in toolbar bt = $("
#{com[0]}
") if com[2]? atom.tooltips.add bt, title: com[2] @consoleToolbar.prepend bt caller = this bt.click () -> caller.onCommand $(this).data('action') return this commandLineNotCounted: () -> @inputLine-- parseSpecialStringTemplate: (prompt, values, isDOM=false) => if isDOM return ATPVariablesBuiltins.parseHtml(this, prompt, values) else return ATPVariablesBuiltins.parse(this, prompt, values) getCommandPrompt: (cmd) -> return @parseTemplate atom.config.get('terminalix.commandPrompt'), {cmd: cmd}, true delay: (callback, delay=100) -> setTimeout callback, delay execDelayedCommand: (delay, cmd, args, state) -> caller = this callback = -> caller.exec cmd, args, state setTimeout callback, delay moveToCurrentDirectory: ()-> CURRENT_LOCATION = @getCurrentFileLocation() if CURRENT_LOCATION? @cd [CURRENT_LOCATION] else if atom.project.getDirectories()[0]? @cd [atom.project.getDirectories()[0].path] getCurrentFileName: ()-> current_file = @getCurrentFile() if current_file? return current_file.getBaseName() return null getCurrentFileLocation: ()-> current_file = @getCurrentFile() if current_file? return current_file.getPath().replace ///#{current_file.getBaseName()}$///, "" getCurrentFilePath: ()-> current_file = @getCurrentFile() if current_file? return current_file.getPath() return null getCurrentFile: ()-> if not atom.workspace? return null te = atom.workspace.getActiveTextEditor() if te? if te.getPath()? return te.buffer.file return null parseTemplate: (text, vars, isDOM=false) -> if not vars? vars = {} ret = '' if isDOM ret = ATPVariablesBuiltins.parseHtml this, text, vars else ret = @parseSpecialStringTemplate text, vars ret = @util.replaceAll '%(file-original)', @getCurrentFilePath(), ret ret = @util.replaceAll '%(cwd-original)', @getCwd(), ret ret = @util.replaceAll '&fs;', '/', ret ret = @util.replaceAll '&bs;', '\\', ret return ret parseExecToken__: (cmd, args, strArgs) -> if strArgs? cmd = @util.replaceAll "%(*)", strArgs, cmd cmd = @util.replaceAll "%(*^)", (@util.replaceAll "%(*^)", "", cmd), cmd if args? argsNum = args.length for i in [0..argsNum] by 1 if args[i]? v = args[i].replace /\n/ig, '' cmd = @util.replaceAll "%(#{i})", args[i], cmd cmd = @parseTemplate cmd, {file:@getCurrentFilePath()} return cmd execStackCounter: 0 exec: (cmdStr, ref_args, state, callback) -> if not state? state = this if not ref_args? ref_args = {} if cmdStr.split? cmdStrC = cmdStr.split ';;' if cmdStrC.length > 1 cmdStr = cmdStrC @execStackCounter = 0 return @exec_ cmdStr, ref_args, state, callback exec_: (cmdStr, ref_args, state, callback) -> if not callback? callback = () -> return null ++@execStackCounter if cmdStr instanceof Array ret = '' for com in cmdStr val = @exec com, ref_args, state if val? ret += val --@execStackCounter if @execStackCounter==0 callback() if not ret? return null return ret else cmdStr = @util.replaceAll "\\\"", '&hquot;', cmdStr cmdStr = @util.replaceAll "&bs;\"", '&hquot;', cmdStr cmdStr = @util.replaceAll "\\\'", '&lquot;', cmdStr cmdStr = @util.replaceAll "&bs;\'", '&lquot;', cmdStr ref_args_str = null if ref_args? if ref_args.join? ref_args_str = ref_args.join(' ') cmdStr = @parseExecToken__ cmdStr, ref_args, ref_args_str args = [] cmd = cmdStr cmd.replace /("[^"]*"|'[^']*'|[^\s'"]+)/g, (s) => if s[0] != '"' and s[0] != "'" s = s.replace /~/g, @userHome s = @util.replaceAll '&hquot;', '"', s s = @util.replaceAll '&lquot;', '\'', s args.push s args = @util.dir args, @getCwd() cmd = args.shift() command = null if @isCommandEnabled(cmd) command = ATPCore.findUserCommand(cmd) if command? if not state? ret = null throw new Error 'The console functional (not native) command cannot be executed without caller information: \''+cmd+'\'.' if command? try ret = command(state, args) catch e throw new Error "Error at executing terminal command: '#{cmd}' ('#{cmdStr}'): #{e.message}" --@execStackCounter if @execStackCounter==0 callback() if not ret? return null return ret else if atom.config.get('terminalix.enableExtendedCommands') or @specsMode if @isCommandEnabled(cmd) command = @getLocalCommand(cmd) if command? ret = command(state, args) --@execStackCounter if @execStackCounter==0 callback() if not ret? return null return ret else cmdStr = @util.replaceAll '&hquot;', '"', cmdStr cmd = @util.replaceAll '&hquot;', '"', cmd cmdStr = @util.replaceAll '&lquot;', '\'', cmdStr cmd = @util.replaceAll '&lquot;', '\'', cmd #@spawn cmdStr, cmd, args @spawnR cmdStr, cmd, args --@execStackCounter #if @execStackCounter==0 # callback() if not cmd? return null return null isCommandEnabled: (name) -> disabledCommands = atom.config.get('terminalix.disabledExtendedCommands') or @specsMode if not disabledCommands? return true if name in disabledCommands return false return true getLocalCommand: (name) -> for cmd_name, cmd_body of @localCommands if cmd_name == name if cmd_body.command? return cmd_body.command else return cmd_body return null getCommandsRegistry: () -> global_vars = ATPVariablesBuiltins.list for key, value of process.env global_vars['%(env.'+key+')'] = "access native environment variable: "+key cmd = [] for cmd_name, cmd_body of @localCommands cmd.push { name: cmd_name description: cmd_body.description example: cmd_body.example params: cmd_body.params deprecated: cmd_body.deprecated sourcefile: cmd_body.sourcefile source: cmd_body.source or 'internal' } for cmd_name, cmd_body of ATPCore.getUserCommands() cmd.push { name: cmd_name description: cmd_body.description example: cmd_body.example params: cmd_body.params deprecated: cmd_body.deprecated sourcefile: cmd_body.sourcefile source: 'external' } for var_name, descr of global_vars cmd.push { name: var_name description: descr source: 'global-variable' } cmd_ = [] cmd_len = cmd.length cmd_forbd = (atom.config.get 'terminalix.disabledExtendedCommands') or [] for cmd_item in cmd if cmd_item.name in cmd_forbd else cmd_.push cmd_item return cmd_ getCommandsNames: () -> cmds = @getCommandsRegistry() cmd_names = [] for item in cmds descr = "" example = "" params = "" sourcefile = "" deprecated = false name = item.name if item.sourcefile? sourcefile = "
Plugin #{item.sourcefile}   
" if item.example? example = "
Example:
"+item.example+"" if item.params? params = item.params if item.deprecated deprecated = true icon_style = '' descr_prefix = '' if item.source == 'external' icon_style = 'book' descr_prefix = 'External: ' else if item.source == 'internal' icon_style = 'repo' descr_prefix = 'Builtin: ' else if item.source == 'internal-atom' icon_style = 'repo' descr_prefix = 'Atom command: ' else if item.source == 'external-functional' icon_style = 'plus' descr_prefix = 'Functional: ' else if item.source == 'global-variable' icon_style = 'briefcase' descr_prefix = 'Global variable: ' if deprecated name = ""+name+"" descr = "
#{name} #{params}
#{item.description} #{example} #{sourcefile}
" cmd_names.push { name: item.name description: descr html: true } return cmd_names getLocalCommandsMemdump: () -> cmd = @getCommandsRegistry() commandFinder = new ATPCommandFinderView cmd commandFinderPanel = atom.workspace.addModalPanel(item: commandFinder) commandFinder.shown commandFinderPanel, this return commandProgress: (value) -> if value < 0 @cliProgressBar.hide() @cliProgressBar.attr('value', '0') else @cliProgressBar.show() @cliProgressBar.attr('value', value/2) showInitMessage: (forceShow=false) -> if not forceShow if @helloMessageShown return if atom.config.get 'terminalix.enableConsoleStartupInfo' or forceShow or (not @specsMode) changelog_path = require("path").join(__dirname, "../CHANGELOG.md") readme_path = require("path").join(__dirname, "../README.md") hello_message = @consolePanel 'ATOM Terminal', 'Please enter new commands to the box below. (ctrl-to show suggestions dropdown)
The console supports special annotation like: %(path), %(file), %(link)file.something%(endlink).
It also supports special HTML elements like: %(tooltip:A:content:B) and so on.
Hope you\'ll enjoy the terminal.'+ "
See changelog  and the README! :)" @rawMessage hello_message $('.changelog-link').css('font-weight','300%').click(() -> atom.workspace.open changelog_path ) $('.readme-link').css('font-weight','300%').click(() -> atom.workspace.open readme_path ) @helloMessageShown = true return this clearStatusIcon: () -> @statusIcon.removeClass() @statusIcon.addClass('atp-panel icon icon-terminal') onCommand: (inputCmd) -> @fsSpy() @rawMessage @inputBoxState() @removeInputBox() @clearStatusIcon() if not inputCmd? inputCmd = @readInputBox() @disposables.dispose('statusIconTooltips') @disposables.add 'statusIconTooltips', atom.tooltips.add @statusIcon, title: 'Task: \"'+inputCmd+'\"' delay: 0 animation: false @inputLine++ inputCmd = @parseSpecialStringTemplate inputCmd if @echoOn console.log 'echo-on' #AllOPenPointsTerminalix:0 Repair! #@message "\n"+@getCommandPrompt(inputCmd)+" "+inputCmd+"\n", false ret = @exec inputCmd, null, this, () => @putInputBox() @showCmd() if typeof ret is 'string' @message ret + '\n' return null initialize: -> @userHome = process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE cmd = 'test -e /etc/profile && source /etc/profile;test -e ~/.profile && source ~/.profile; node -pe "JSON.stringify(process.env)"' exec cmd, (code, stdout, stderr) -> try process.env = JSON.parse(stdout) catch e atom.commands.add 'atom-workspace', "atp-status:toggle-output": => @toggle() clear: -> @cliOutput.empty() @putInputBox() setMaxWindowHeight: -> maxHeight = atom.config.get('terminalix.WindowHeight') @cliOutput.css("max-height", "#{maxHeight}px") $('.terminal-input').css("max-height", "#{maxHeight}px") showCmd: -> @focusInputBox() @scrollToBottom() scrollToBottom: -> @cliOutput.scrollTop 10000000 flashIconClass: (className, time=100)=> @statusIcon.addClass className @timer and clearTimeout(@timer) onStatusOut = => @statusIcon.removeClass className @timer = setTimeout onStatusOut, time destroy: -> @statusIcon.remove() _destroy = => if @hasParent() @close() if @statusIcon and @statusIcon.parentNode @statusIcon.parentNode.removeChild(@statusIcon) @statusView.removeCommandView this if @program @program.once 'exit', _destroy @program.kill() else _destroy() terminateProcessTree: () -> pid = @program.pid psTree = require 'ps-tree' killProcess = (pid, signal, callback) -> signal = signal || 'SIGKILL' callback = callback || () -> {} killTree = true if killTree psTree(pid, (err, children) -> [pid].concat( children.map((p) -> return p.PID ) ).forEach((tpid) -> try process.kill tpid, signal catch ex ) callback() ) else try process.kill pid, signal catch ex callback() killProcess pid, 'SIGINT' kill: -> if @program @terminateProcessTree @program.pid @program.stdin.pause() @program.kill('SIGINT') @program.kill() maximize: -> @cliOutput.height (@cliOutput.height()+9999) open: -> if (atom.config.get('terminalix.moveToCurrentDirOnOpen')) and (not @specsMode) @moveToCurrentDirectory() if (atom.config.get('terminalix.moveToCurrentDirOnOpenLS')) and (not @specsMode) @clear() @execDelayedCommand @_cmdintdel, 'ls', null, this @configFileFtp = findConfig.read('.ftpconfig') if @configFileFtp #Get document, or throw exception on error try configData = yaml.safeLoad(@configFileFtp) console.log("Found version"+configData.version) #@execDelayedCommand @_cmdintdel, 'ssh', ['aa','bb'], this #yaml.safeLoad(fs.readFileSync('/home/ixti/example.yml', 'utf8')) catch e console.log("Error reading .ftpconfig file: "+e) else console.log("Config file not found") atom.workspace.addBottomPanel(item: this) unless @hasParent() if lastOpenedView and lastOpenedView != this lastOpenedView.close() lastOpenedView = this @setMaxWindowHeight() @putInputBox() if not @spawnProcessActive @scrollToBottom() @statusView.setActiveCommandView this @focusInputBox() @showInitMessage() atom.tooltips.add @killBtn, title: 'Kill the long working process.' atom.tooltips.add @exitBtn, title: 'Destroy the terminal session.' atom.tooltips.add @closeBtn, title: 'Hide the terminal window.' atom.tooltips.add @openConfigBtn, title: 'Open the terminal config file.' atom.tooltips.add @reloadConfigBtn, title: 'Reload the terminal configuration.' if atom.config.get 'terminalix.enableWindowAnimations' @WindowMinHeight = @cliOutput.height() + 50 @height 0 @consoleToolbarHeading.css {opacity: 0} @animate { height: @WindowMinHeight }, 250, => @attr 'style', '' @consoleToolbarHeading.animate { opacity: 1 }, 250, => @consoleToolbarHeading.attr 'style', '' close: -> if atom.config.get 'terminalix.enableWindowAnimations' @WindowMinHeight = @cliOutput.height() + 50 @height @WindowMinHeight @animate { height: 0 }, 250, => @attr 'style', '' @consoleToolbar.attr 'style', '' @detach() lastOpenedView = null else @detach() lastOpenedView = null toggle: -> if @hasParent() @close() else @open() removeQuotes: (text)-> if not text? return '' if text instanceof Array ret = [] for t in text ret.push (@removeQuotes t) return ret return text.replace(/['"]+/g, '') cd: (args)-> args = [atom.project.getPaths()[0]] if not args[0] args = @removeQuotes args dir = resolve @getCwd(), args[0] try stat = fs.statSync dir if not stat.isDirectory() return @errorMessage "cd: not a directory: #{args[0]}" @cwd = dir catch e return @errorMessage "cd: #{args[0]}: No such file or directory" return null ls: (args) -> try files = fs.readdirSync @getCwd() catch e return false if atom.config.get('terminalix.XExperimentEnableForceLinking') ret = '' files.forEach (filename) => ret += @resolvePath filename + '\t%(break)' @message ret return true filesBlocks = [] files.forEach (filename) => filesBlocks.push @_fileInfoHtml(filename, @getCwd()) filesBlocks = filesBlocks.sort (a, b) -> aDir = false bDir = false if a[1]? aDir = a[1].isDirectory() if b[1]? bDir = b[1].isDirectory() if aDir and not bDir return -1 if not aDir and bDir return 1 a[2] > b[2] and 1 or -1 filesBlocks.unshift @_fileInfoHtml('..', @getCwd()) filesBlocks = filesBlocks.map (b) -> b[0] @message filesBlocks.join('%(break)') + '
' return true parseSpecialNodes: () -> caller = this if atom.config.get 'terminalix.enableConsoleInteractiveHints' $('.atp-tooltip[data-toggle="tooltip"]').each(() -> title = $(this).attr('title') atom.tooltips.add $(this), {} ) if atom.config.get 'terminalix.enableConsoleInteractiveLinks' @find('.console-link').each ( () -> el = $(this) link_target = el.data('target') if link_target != null && link_target != undefined el.data('target', null) link_type = el.data('targettype') link_target_name = el.data('targetname') link_target_line = el.data('line') link_target_column = el.data('column') if not link_target_line? link_target_line = 0 if not link_target_column? link_target_column = 0 el.click () -> el.addClass('link-used') if link_type == 'file' atom.workspace.open link_target, { initialLine: link_target_line initialColumn: link_target_column } if link_type == 'directory' moveToDir = (directory, messageDisp=false)-> caller.clear() caller.cd([directory]) setTimeout () -> if not caller.ls() if not messageDisp caller.errorMessage 'The directory is inaccesible.\n' messageDisp = true setTimeout () -> moveToDir('..', messageDisp) , 1500 , caller._cmdintdel setTimeout () -> moveToDir(link_target_name) , caller._cmdintdel ) # el.data('filenameLink', '') consoleAlert: (text) -> return '' consolePanel: (title, content) -> return '
'+title+'
'+content+'


' consoleText: (type, text) -> if type == 'info' return ''+text+'' if type == 'error' return ''+text+'' if type == 'warning' return ''+text+'' if type == 'success' return ''+text+'' return text consoleLabel: (type, text) -> if (not atom.config.get 'terminalix.enableConsoleLabels') and (not @specsMode) return text if not text? text = type if type == 'badge' return ''+text+'' if type == 'default' return ''+text+'' if type == 'primary' return ''+text+'' if type == 'success' return ''+text+'' if type == 'info' return ''+text+'' if type == 'warning' return ''+text+'' if type == 'danger' return ''+text+'' if type == 'error' return ''+text+'' return ''+text+'' consoleLink: (name, forced=true) -> if (atom.config.get 'terminalix.XExperimentEnableForceLinking') and (not forced) return name return @_fileInfoHtml(name, @getCwd(), 'font', false)[0] _fileInfoHtml: (filename, parent, wrapper_class='span', use_file_info_class='true') -> str = filename name_tokens = filename filename = filename.replace /:[0-9]+:[0-9]/ig, '' name_tokens = @util.replaceAll filename, '', name_tokens name_tokens = name_tokens.split ':' fileline = name_tokens[0] filecolumn = name_tokens[1] filename = @util.replaceAll '/', '\\', filename filename = @util.replaceAll parent, '', filename filename = @util.replaceAll (@util.replaceAll '/', '\\', parent), '', filename if filename[0] == '\\' or filename[0] == '/' filename = filename.substring(1) if filename == '..' if use_file_info_class return ["<#{wrapper_class} data-targetname=\"#{filename}\" data-targettype=\"directory\" data-target=\"#{filename}\" class=\"console-link icon-file-directory parent-folder\">#{filename}", null, filename] else return ["<#{wrapper_class} data-targetname=\"#{filename}\" data-targettype=\"directory\" data-target=\"#{filename}\" class=\"console-link icon-file-directory file-info parent-folder\">#{filename}", null, filename] file_exists = true filepath = @resolvePath filename classes = [] dataname = '' if atom.config.get('terminalix.useAtomIcons') classes.push 'name' classes.push 'icon' dataname = filepath else classes.push 'name' if use_file_info_class classes.push 'file-info' stat = null if file_exists try stat = fs.lstatSync filepath catch e file_exists = false if file_exists if atom.config.get('terminalix.enableConsoleInteractiveLinks') or @specsMode classes.push 'console-link' if stat.isSymbolicLink() classes.push 'stat-link' stat = fs.statSync filepath target_type = 'null' if stat.isFile() if stat.mode & 73 #0111 classes.push 'stat-program' # TODO:10 check extension matcher = /(.:)((.*)\\)*((.*\.)*)/ig extension = filepath.replace matcher, "" classes.push @util.replaceAll(' ', '', extension) classes.push 'icon-file-text' target_type = 'file' if stat.isDirectory() classes.push 'icon-file-directory' target_type = 'directory' if stat.isCharacterDevice() classes.push 'stat-char-dev' target_type = 'device' if stat.isFIFO() classes.push 'stat-fifo' target_type = 'fifo' if stat.isSocket() classes.push 'stat-sock' target_type = 'sock' else classes.push 'file-not-found' classes.push 'icon-file-text' target_type = 'file' if filename[0] == '.' classes.push 'status-ignored' target_type = 'ignored' href = 'file:///' + @util.replaceAll('\\', '/', filepath) classes.push 'atp-tooltip' exattrs = [] if fileline? exattrs.push 'data-line="'+fileline+'"' if filecolumn? exattrs.push 'data-column="'+filecolumn+'"' filepath_tooltip = @util.replaceAll '\\', '/', filepath filepath = @util.replaceAll '\\', '/', filepath ["<#{wrapper_class} #{exattrs.join ' '} tooltip=\"\" data-targetname=\"#{filename}\" data-targettype=\"#{target_type}\" data-target=\"#{filepath}\" data-name=\"#{dataname}\" class=\"#{classes.join ' '}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"#{filepath_tooltip}\" >#{filename}", stat, filename] getGitStatusName: (path, gitRoot, repo) -> status = (repo.getCachedPathStatus or repo.getPathStatus)(path) if status if repo.isStatusModified status return 'modified' if repo.isStatusNew status return 'added' if repo.isPathIgnore path return 'ignored' preserveOriginalPaths: (text) -> text = @util.replaceAll @getCurrentFilePath(), '%(file-original)', text text = @util.replaceAll @getCwd(), '%(cwd-original)', text text = @util.replaceAll @getCwd(), '%(cwd-original)', text text = @util.replaceAll '/', '&fs;', text text = @util.replaceAll '\\', '&bs;', text return text parseMessage: (message, matchSpec=true, parseCustomRules=true) -> instance = this message = '
'+(instance.parseMessage_ message, false, true, true)+'
' n = $(message) n.contents().filter(() -> return this.nodeType == 3 ).each(() -> thiz = $(this) out = thiz.text() out = instance.parseMessage_ out, matchSpec, parseCustomRules thiz.replaceWith(''+out+'') ) return n.html() parseMessage_: (message, matchSpec=true, parseCustomRules=true, isForcelyPreparsering=false) -> if message == null return '' if matchSpec if atom.config.get('terminalix.XExperimentEnableForceLinking') if atom.config.get('terminalix.textReplacementFileAdress')? if atom.config.get('terminalix.textReplacementFileAdress') != '' # regex = /(([A-Za-z]:)(\\|\/))?([A-Za-z$\*\-+&#@!_\.]+(\\|\/))([A-Za-z $\*\-+&#@!_\.]+(\\|\/))*[A-Za-z\-_$\*\+&\^@#\. ]+\.[A-Za-z\-_$\*\+]*/ig # regex = /(([A-Za-z]:)(\\|\/))?(([^\s#@$%&!;<>\.\^:]| )+(\\|\/))((([^\s#@$%&!;<>\.\^:]| )+(\\|\/))*([^\s<>:#@$%\^;]| )+(\.([^\s#@$%&!;<>\.0-9:\^]| )*)*)?/ig regex = /(\.(\\|\/))?(([A-Za-z]:)(\\|\/))?(([^\s#@$%&!;<>\.\^:]| )+(\\|\/))((([^\s#@$%&!;<>\.\^:]| )+(\\|\/))*([^\s<>:#@$%\^;]| )+(\.([^\s#@$%&!;<>\.0-9:\^]| )*)*)?/ig regex2 = /(\.(\\|\/))((([^\s#@$%&!;<>\.\^:]| )+(\\|\/))*([^\s<>:#@$%\^;]| )+(\.([^\s#@$%&!;<>\.0-9:\^]| )*)*)?/ig message = message.replace regex, (match, text, urlId) => return @parseSpecialStringTemplate atom.config.get('terminalix.textReplacementFileAdress'), {file:match} message = message.replace regex2, (match, text, urlId) => return @parseSpecialStringTemplate atom.config.get('terminalix.textReplacementFileAdress'), {file:match} else if atom.config.get('terminalix.textReplacementFileAdress')? if atom.config.get('terminalix.textReplacementFileAdress') != '' #regex = /(([A-Za-z]:)(\\|\/))?([A-Za-z$\*\-+&#@!_\.]+(\\|\/))([A-Za-z $\*\-+&#@!_\.]+(\\|\/))*[A-Za-z\-_$\*\+&\^@#\. ]+\.[A-Za-z\-_$\*\+]*/ig cwdN = @getCwd() cwdE = @util.replaceAll '/', '\\', @getCwd() regexString ='(' + (@util.escapeRegExp cwdN) + '|' + (@util.escapeRegExp cwdE) + ')\\\\([^\\s:#$%^&!:]| )+\\.?([^\\s:#$@%&\\*\\^!0-9:\\.+\\-,\\\\\\/\"]| )*' regex = new RegExp(regexString, 'ig') message = message.replace regex, (match, text, urlId) => return @parseSpecialStringTemplate atom.config.get('terminalix.textReplacementFileAdress'), {file:match} if atom.config.get('terminalix.textReplacementCurrentFile')? if atom.config.get('terminalix.textReplacementCurrentFile') != '' path = @getCurrentFilePath() regex = new RegExp @util.escapeRegExp(path), 'g' message = message.replace regex, (match, text, urlId) => return @parseSpecialStringTemplate atom.config.get('terminalix.textReplacementCurrentFile'), {file:match} message = @preserveOriginalPaths message if atom.config.get('terminalix.textReplacementCurrentPath')? if atom.config.get('terminalix.textReplacementCurrentPath') != '' path = @getCwd() regex = new RegExp @util.escapeRegExp(path), 'g' message = message.replace regex, (match, text, urlId) => return @parseSpecialStringTemplate atom.config.get('terminalix.textReplacementCurrentPath'), {file:match} message = @util.replaceAll '%(file-original)', @getCurrentFilePath(), message message = @util.replaceAll '%(cwd-original)', @getCwd(), message message = @util.replaceAll '&fs;', '/', message message = @util.replaceAll '&bs;', '\\', message rules = ATPCore.getConfig().rules for key, value of rules matchExp = key replExp = '%(content)' matchAllLine = false matchNextLines = 0 flags = 'gm' forceParse = false if value.match? if value.match.flags? flags = value.match.flags.join '' if value.match.replace? replExp = value.match.replace if value.match.matchLine? matchAllLine = value.match.matchLine if value.match.matchNextLines? matchNextLines = value.match.matchNextLines if value.match.forced? forceParse = value.match.forced if (forceParse or parseCustomRules) and ((isForcelyPreparsering and forceParse) or (not isForcelyPreparsering)) if matchAllLine matchExp = '.*' + matchExp if matchNextLines > 0 for i in [0..matchNextLines] by 1 matchExp = matchExp + '[\\r\\n].*' regex = new RegExp(matchExp, flags) message = message.replace regex, (match, groups...) => style = '' if value.css? style = ATPCore.jsonCssToInlineStyle value.css else if not value.match? style = ATPCore.jsonCssToInlineStyle value vars = content: match 0: match groupsNumber = groups.length-1 for i in [0..groupsNumber] by 1 if groups[i]? vars[i+1] = groups[i] # console.log 'Active rule => '+matchExp repl = @parseSpecialStringTemplate replExp, vars return "#{repl}" message = @util.replaceAll '%(file-original)', @getCurrentFilePath(), message message = @util.replaceAll '%(cwd-original)', @getCwd(), message message = @util.replaceAll '&fs;', '/', message message = @util.replaceAll '&bs;', '\\', message return message redirect: (streamName) -> @redirectOutput = streamName rawMessage: (message) -> if @redirectOutput == 'console' console.log message return @cliOutput.append message @showCmd() # @parseSpecialNodes() message: (message, matchSpec=true) -> if @redirectOutput == 'console' console.log message return if typeof message is 'object' mes = message else if not message? return mes = message.split '%(break)' if mes.length > 1 for m in mes @message m return else mes = mes[0] mes = @parseMessage message, matchSpec, matchSpec mes = @util.replaceAll '%(raw)', '', mes mes = @parseTemplate mes, [], true # mes = @util.replaceAll '<', '<', mes # mes = @util.replaceAll '>', '>', mes @cliOutput.append mes @showCmd() @parseSpecialNodes() @scrollToBottom() # @putInputBox() errorMessage: (message) -> @cliOutput.append @parseMessage(message) @clearStatusIcon() @statusIcon.addClass 'status-error' @parseSpecialNodes() correctFilePath: (path) -> return @util.replaceAll '\\', '/', path getCwd: -> if not atom.project? return null extFile = extname atom.project.getPaths()[0] if extFile == "" if atom.project.getPaths()[0] projectDir = atom.project.getPaths()[0] else if process.env.HOME projectDir = process.env.HOME else if process.env.USERPROFILE projectDir = process.env.USERPROFILE else projectDir = '/' else projectDir = dirname atom.project.getPaths()[0] cwd = @cwd or projectDir or @userHome return @correctFilePath cwd spawn: (inputCmd, cmd, args) => ## @cmdEditor.hide() ## htmlStream = ansihtml() # htmlStream = iconv.decodeStream @streamsEncoding # htmlStream.on 'data', (data) => ## @cliOutput.append data # @message data # @scrollToBottom() # try ## @program = spawn cmd, args, stdio: 'pipe', env: process.env, cwd: @getCwd() # @program = exec inputCmd, stdio: 'pipe', env: process.env, cwd: @getCwd() ## @program.stdin.pipe htmlStream # @program.stdout.pipe htmlStream # @program.stderr.pipe htmlStream ## @program.stdout.setEncoding @streamsEncoding @spawnProcessActive = true instance = this dataCallback = (data) -> instance.message(data) instance.scrollToBottom() processCallback = (error, stdout, stderr) -> console.log 'callback' if atom.config.get('terminalix.logConsole') or @specsMode console.log(error, stdout, stderr) instance.putInputBox() instance.showCmd() htmlStream = ansihtml() htmlStream = iconv.decodeStream @streamsEncoding htmlStream.on 'data', dataCallback try if @configFileFtp @program = process @programSSH = execSSH('echo dada', { user: 'admin123' host: 'vps123.ovh.net' passphrase: '123' key: 'c:/users/user1/.ssh/id_rsa' password: '123-2000' timeout: 10000 }, processCallback) console.log(@programSSH) @programSSH.pipe @program.stdout @program.stdin.pipe htmlStream #(@program.stdin.pipe iconv.encodeStream 'base64').pipe htmlStream #@program = execSSH inputCmd, stdio: 'pipe', env: process.env, cwd: @getCwd(), processCallback else @program = exec inputCmd, stdio: 'pipe', env: process.env, cwd: @getCwd(), processCallback @program.stdout.setEncoding 'base64' console.log @program if atom.config.get('terminalix.logConsole') or @specsMode (@program.stdout.pipe iconv.encodeStream 'base64').pipe htmlStream @clearStatusIcon() @statusIcon.addClass 'status-running' @killBtn.removeClass 'hide' @program.on 'exit', (code, signal) => console.log 'exit', code, signal if atom.config.get('terminalix.logConsole') or @specsMode @killBtn.addClass 'hide' @statusIcon.removeClass 'status-running' if code == 0 @statusIcon.addClass 'status-success' else if code? @statusIcon.addClass 'status-error' if code == 127 @message (@consoleLabel 'error', 'Error')+(@consoleText 'error', cmd + ': command not found') @message '\n' @program = null @spawnProcessActive = false @program.on 'error', (err) => console.log 'error' if atom.config.get('terminalix.logConsole') or @specsMode @message (err.message) @message '\n' @putInputBox() @showCmd() @statusIcon.addClass 'status-error' @program.stdout.on 'data', (data) => @flashIconClass 'status-info' @statusIcon.removeClass 'status-error' @program.stderr.on 'data', (data) => console.log 'stderr' if atom.config.get('terminalix.logConsole') or @specsMode @message data @flashIconClass 'status-error', 300 #DOING:20 x 2017-08-09 This task was created on 2017-08-09 and complited 2017-08-23 catch err console.log 'Failed to launch process' if atom.config.get('terminalix.logConsole') or @specsMode # IDEA: test it @message ('haha '+err.message) #DOING:21 This task is due on 2017-08-24 due:2017-08-24 @showCmd() spawnR: (inputCmd, cmd, args) => @spawnRProcessActive = true instance = this dataCallback = (data) -> instance.message(data) instance.scrollToBottom() processCallback = (error, stdout, stderr) -> console.log 'callback' if atom.config.get('terminalix.logConsole') or @specsMode console.log(error, stdout, stderr) instance.putInputBox() instance.showCmd() htmlStream = ansihtml() htmlStream = iconv.decodeStream @streamsEncoding htmlStream.on 'data', dataCallback sshCallback = (err, ssh) => console.log 'ssh' console.log ssh console.log err try if err @program = new EventEmitter() @program.emit('error',err) processCallback(err,null,null) else @program = execR {cmd: 'ls -lah', ssh: ssh}, (err, stdout, stderr) -> console.log ssh console.log 'err:' console.log err console.log stderr console.log stdout #@program = exec inputCmd, stdio: 'pipe', env: process.env, cwd: @getCwd(), processCallback @program.stdout.setEncoding 'base64' console.log @program if atom.config.get('terminalix.logConsole') or @specsMode (@program.stdout.pipe iconv.encodeStream 'base64').pipe htmlStream @clearStatusIcon() @statusIcon.addClass 'status-running' @killBtn.removeClass 'hide' @program.on 'exit', (code, signal) => console.log 'exit', code, signal if atom.config.get('terminalix.logConsole') or @specsMode @killBtn.addClass 'hide' @statusIcon.removeClass 'status-running' if code == 0 @statusIcon.addClass 'status-success' else if code? @statusIcon.addClass 'status-error' if code == 127 @message (@consoleLabel 'error', 'Error')+(@consoleText 'error', cmd + ': command not found') @message '\n' @program = null @spawnRProcessActive = false @program.on 'error', (err) => console.log 'error' if atom.config.get('terminalix.logConsole') or @specsMode @message (err.message) @message '\n' @putInputBox() @showCmd() @statusIcon.addClass 'status-error' @flashIconClass 'status-error', 300 @program.stdout.on 'data', (data) => @flashIconClass 'status-info' @statusIcon.removeClass 'status-error' @program.stderr.on 'data', (data) => console.log 'stderr' if atom.config.get('terminalix.logConsole') or @specsMode @message data @flashIconClass 'status-error', 300 #DOING:20 x 2017-08-09 This task was created on 2017-08-09 and complited 2017-08-23 catch err1 console.log 'Failed to launch process' if atom.config.get('terminalix.logConsole') or @specsMode console.log err1 # IDEA: test it @message err1.message #DOING:21 This task is due on 2017-08-24 due:2017-08-24 @showCmd() try connect { user: 'admin123' host: 'vps123.ovh.net' #passphrase: '123' #key: 'c:/users/user1/.ssh/id_rsa' password: '123-2000' timeout: 10000 }, sshCallback catch err2 console.log err2 # coffeeli1111nt: enable=* # coffeelint: enable=max_line_length