diff --git a/lib/typescript-import.coffee b/lib/typescript-import.coffee
index 591c19a..2b647b6 100644
--- a/lib/typescript-import.coffee
+++ b/lib/typescript-import.coffee
@@ -1,274 +1,287 @@
-SubAtom = require 'sub-atom';
-
-{CompositeDisposable} = require 'atom'
-
-module.exports = TypescriptImport =
- modalPanel: null
- subscriptions: null
-
- activate: (state) ->
- # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
- @subscriptions = new CompositeDisposable
-
- # Register command that toggles this view
- @subscriptions.add atom.commands.add 'atom-workspace', 'typescript-import:insert': => @insert()
- @subscriptions.add atom.commands.add 'atom-workspace', 'typescript-import:build-index': => @buildIndex()
- @subscriptions.add atom.commands.add 'atom-workspace', 'typescript-import:go-to-declaration': => @goToDeclaration()
-
- @index = state || {};
- @sub = new SubAtom()
- #bindEvent = @bindEvent
- @sub.add(atom.workspace.observeTextEditors((editor) =>
- @bindEvent(editor)
- ))
-
- bindEvent: (editor) ->
- # console.log('bound event')
- editorView = atom.views.getView(editor)
-# editorView.on 'click.atom-hack',(e)=>
-# console.log (editor.getCursorBufferPosition())
-
- @sub.add(editorView, 'click', (e) =>
- if (e.metaKey || e.ctrlKey)
- @goToDeclaration()
- )
-
-
- deactivate: ->
- @modalPanel.destroy()
- @subscriptions.dispose()
-
- serialize: ->
- @index
-
- goToDeclaration: ->
- editor = atom.workspace.getActiveTextEditor()
- position = editor.getCursorBufferPosition();
- editor.selectWordsContainingCursors();
- selection = editor.getSelectedText().trim()
- editor.setCursorBufferPosition(position);
- symbol = @index[selection]
- if symbol and selection
- atom.workspace.open(symbol.path)
- else
- atom.commands.dispatch(document.querySelector('atom-text-editor'), 'typescript:go-to-declaration')
-
-
- addImportStatement: (importedSymbol, relativePath, isDefaultImport) ->
- editor = atom.workspace.getActiveTextEditor()
- currentText = editor.getText()
-
- reImports = /import(.|\r|\n)+?from(.*)$/gm;
- isDefined = false
- hasImportStatements = false;
- lastMatchIndex = 0
- while(!isDefined && importMatch = reImports.exec(currentText))
- if !@isInComment(importMatch.index, lastMatchIndex, currentText)
- lastMatchIndex = importMatch.index
- hasImportStatements = true
- #TODO check for comments using lastImport
- lastImport = importMatch[0]
- if @containsSymbol(importMatch, importedSymbol)
- isDefined = true
- #TODO handle insertion of default imports (currently: create a new import statement if default import)
- else if !isDefaultImport && @isImportFromFile(importMatch, relativePath)
- newStatement = @insertIntoStatement(importMatch, importedSymbol, isDefaultImport)
- existingImportStatement = {match: importMatch, statement: newStatement}
- else
- lastMatchIndex = importMatch.index
-
- if isDefined
- atom.notifications.addWarning('Import '+importedSymbol+' is already defined.');
- else
- currentPosition = editor.getCursorBufferPosition()
- if hasImportStatements
- if existingImportStatement
- newStatement = existingImportStatement.statement
- importMatch = existingImportStatement.match
- prefixEnd = importMatch.index
- suffixStart = prefixEnd + importMatch[0].length
- currentText = currentText.substring(0, prefixEnd) + newStatement + currentText.substring(suffixStart)
- else
- importStatement = @createNewImportStatement(importedSymbol, relativePath, isDefaultImport)
- currentText = currentText.replace(lastImport, lastImport + importStatement);
- else
- importStatement = @createNewImportStatement(importedSymbol, relativePath, isDefaultImport)
- referencesMatches= currentText.match(/\/\/\/\s*\s*$/gm)
- nl = @getNewLineChar()
- if referencesMatches
- lastReference = referencesMatches.pop();
- currentText = currentText.replace(lastReference, lastReference + importStatement + nl);
- else
- useStrictMatche = currentText.match(/.*[\'\"]use strict[\'\"].*/)
- if useStrictMatche
- useStrict = useStrictMatche.pop();
- currentText = currentText.replace(useStrict, useStrict + importStatement + nl);
- else
- currentText = importStatement + currentText;
- editor.setText(currentText);
- currentPosition.row++;
- editor.setCursorBufferPosition(currentPosition)
-
- insert: ->
- editor = atom.workspace.getActiveTextEditor()
- @buildIndex()
- @bindEvent(editor)
- os = require('os')
- path = require('path')
- position = editor.getCursorBufferPosition()
- editor.selectWordsContainingCursors()
- selection = editor.getSelectedText().trim()
- filePath = editor.getPath()
- symbol = @index[selection]
-
- if symbol && selection
- location = symbol.path;
- defaultImport = symbol.defaultImport;
- fileFolder = path.resolve(filePath + '/..');
- relative = path.relative(fileFolder, location).replace(/\.(jsx?|tsx?)$/, '');
- # Replace the windows path seperator with '/'
- if (os.platform() == 'win32')
- relative = relative.split(path.sep).join('/');
-
- if(!/^\./.test(relative))
- relative = './' + relative;
-
- @addImportStatement(selection, relative, defaultImport)
- else
- atom.notifications.addError('Symbol '+selection+' not found.');
-
- createNewImportStatement: (importedSymbol, relativePath, isDefaultImport) ->
- nl = @getNewLineChar()
-
- if(isDefaultImport)
- importStatement = "#{nl}import #{importedSymbol} from '#{relativePath}';"
- else
- importStatement = "#{nl}import { #{importedSymbol} } from '#{relativePath}';"
-
-
- getNewLineChar: ->
- switch atom.config.get('line-ending-selector.defaultLineEnding')
- when 'CRLF' then return '\r\n'
- when 'LF' then return '\n'
- # when 'OS Default' then
- else
- os = require('os')
- if os.platform() == 'win32'
- return '\r\n'
- else
- return '\n'
-
-
- isInComment: (startIndex, previousIndex, text) ->
- if @isInMultiLineComment(startIndex, previousIndex, text)
- return true
- else
- return @isInLineComment(startIndex, previousIndex, text)
-
- isInLineComment: (startIndex, previousIndex, text) ->
- reLineComment = /\/\//g
- reLineComment.lastIndex = previousIndex
-
- #find last line-comment before startIndex
- while (match = reLineComment.exec(text)) && match.index < startIndex
- lastComment = match
-
- #check if last line-comment is in same line as startIndex
- if lastComment
- reNewLine = /\r|\n/g
- reNewLine.lastIndex = lastComment.index
- if match = reNewLine.exec(text)
- #if match.index > startIndex#DEGUB
- # console.warn('import is within line-comment')
- return match.index > startIndex # if the next NL comes after the import statement -> import statement is within the line-comment
-
- return false
-
- isInMultiLineComment: (startIndex, previousIndex, text) ->
- reComment = /\/\*\*?/g
- reClosing = /\*\//
- reComment.lastIndex = previousIndex
- isOpen = false
- #check if last multi-line comment is open for startIndex
- while (commentMatch = reComment.exec(text)) && commentMatch.index < startIndex
- reClosing.lastIndex = commentMatch.index
- if closingMatch = reClosing.exec() && closingMatch.index < startIndex
- isOpen = false
- else
- isOpen = true
-
- #if isOpen#DEBUG
- # console.warn('import in within multi-line-comment')
-
- return isOpen
-
- isImportFromFile: (regexImportStatement, symbolPath) ->
- importPathRaw = regexImportStatement[2]#regarding index-access [2]: see RegExp definition for matchImports in addImportStatement()
- importPathMatch = /'([^']+)'|"([^"]+)"/gm.exec(importPathRaw);
- if importPathMatch
- if importPathMatch[1]
- importPath = importPathMatch[1]
- else
- importPath = importPathMatch[2]
- if !importPath
- console.log('could not extract file path from import statement', regexImportStatement)
- return false
- start = 0
- end = importPath.length
- if /^\.\/\.\./.test(importPath)
- start = 2
- #TODO normalize paths properly
- importPath = importPath.substring(start, end)
- return (importPath == symbolPath)
-
- extractSymbolStringFrom: (regexImportStatement) ->
- importStatement = regexImportStatement[0];
- if /\{/gm.test(importStatement)
- strSymbols = /.*\{([^}]+)\}/g.exec(importStatement)
- else
- strSymbols = /.*?import((.|\r|\n)+?)from/g.exec(importStatement)
-
- containsSymbol: (regexImportStatement, newSymbol) ->
- symbolStrList = @extractSymbolStringFrom(regexImportStatement)
- reTest = new RegExp('\\s*' + newSymbol + '\\s*(,|$)', 'gm')
- i = 0
- size = symbolStrList.length
- while i < size
- if reTest.test(symbolStrList[i])
- return true
- ++i
- return false
-
- insertIntoStatement: (regexImportStatement, newSymbol, isDefaultImport) ->
- #TODO handle default import?: "import {...} form ..." -> "import newSymbol, {...} form ..."
- importStatement = regexImportStatement[0];
- sb = [', ', newSymbol, ' '];
- if /\{/gm.test(importStatement)
- insertIndex = importStatement.lastIndexOf('}');
- else
- insertIndex = /from(.*)$/gm.exec(importStatement).index;#ASSERT there is at least on match, due to RegExp definition in addImportStatement()
- sb[0] = '{ '
- sb.push('}')
- sb.unshift(importStatement.substring(0, insertIndex))
- sb.push(importStatement.substring(insertIndex, importStatement.length))
- return sb.join('')
-
- buildIndex: ->
- index = @index;
- searchPaths = ['**/*.ts', '**/*.js', '**/*.tsx', '**/*.jsx'];
- symbolPattern = /export\s*default\s*(class|interface|namespace|enum|const|type|function)?\s*(([a-zA-Z0-9])*)/
- atom.workspace.scan(symbolPattern, { paths: searchPaths }, (result) ->
- for res in result.matches
- rawSymbol = res.matchText
- symbol = rawSymbol.match(symbolPattern)[2];
- index[symbol] = { path: result.filePath, defaultImport: true };
- );
- symbolPatternNoDefault = /export *(class|interface|namespace|enum|const|type|function)?\s*(([a-zA-Z0-9])*)/
- atom.workspace.scan(symbolPatternNoDefault, { paths: searchPaths }, (result) ->
- for res in result.matches
- rawSymbol = res.matchText
- symbol = rawSymbol.match(symbolPatternNoDefault)[2];
- index[symbol] = { path: result.filePath, defaultImport: false };
- );
- getIndex: ->
- @index
+SubAtom = require 'sub-atom';
+
+{CompositeDisposable, Range} = require 'atom'
+
+module.exports = TypescriptImport =
+ modalPanel: null
+ subscriptions: null
+
+ activate: (state) ->
+ # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
+ @subscriptions = new CompositeDisposable
+
+ # Register command that toggles this view
+ @subscriptions.add atom.commands.add 'atom-workspace', 'typescript-import:insert': => @insert()
+ @subscriptions.add atom.commands.add 'atom-workspace', 'typescript-import:build-index': => @buildIndex()
+ @subscriptions.add atom.commands.add 'atom-workspace', 'typescript-import:go-to-declaration': => @goToDeclaration()
+
+ @index = state || {};
+ @sub = new SubAtom()
+ #bindEvent = @bindEvent
+ @sub.add(atom.workspace.observeTextEditors((editor) =>
+ @bindEvent(editor)
+ ))
+
+ bindEvent: (editor) ->
+ # console.log('bound event')
+ editorView = atom.views.getView(editor)
+# editorView.on 'click.atom-hack',(e)=>
+# console.log (editor.getCursorBufferPosition())
+
+ @sub.add(editorView, 'click', (e) =>
+ if (e.metaKey || e.ctrlKey)
+ @goToDeclaration()
+ )
+
+
+ deactivate: ->
+ @modalPanel.destroy()
+ @subscriptions.dispose()
+
+ serialize: ->
+ @index
+
+ goToDeclaration: ->
+ editor = atom.workspace.getActiveTextEditor()
+ position = editor.getCursorBufferPosition();
+ editor.selectWordsContainingCursors();
+ selection = editor.getSelectedText().trim()
+ editor.setCursorBufferPosition(position);
+ symbol = @index[selection]
+ if symbol and selection
+ atom.workspace.open(symbol.path)
+ else
+ atom.commands.dispatch(document.querySelector('atom-text-editor'), 'typescript:go-to-declaration')
+
+
+ addImportStatement: (importedSymbol, relativePath, isDefaultImport) ->
+ editor = atom.workspace.getActiveTextEditor()
+ currentText = editor.getText()
+
+ reImports = /import(.|\r|\n)+?from(.*)$/gm;
+ isDefined = false
+ hasImportStatements = false;
+ lastMatchIndex = 0
+ existingImportStatement = null
+ lastImport = null
+ lastImportRange = null
+ that = this
+ editor.scan(reImports, (found) ->
+ importMatch = found.match;
+ if (!that.isInComment(importMatch.index, lastMatchIndex, currentText))
+ lastMatchIndex = importMatch.index
+ hasImportStatements = true
+ #TODO check for comments using lastImport
+ lastImport = importMatch[0]
+ lastImportRange = found.range
+ if that.containsSymbol(importMatch, importedSymbol)
+ isDefined = true
+ found.stop()#break
+ #TODO handle insertion of default imports (currently: create a new import statement if default import)
+ else if !isDefaultImport && that.isImportFromFile(importMatch, relativePath)
+ newStatement = that.insertIntoStatement(importMatch, importedSymbol, isDefaultImport)
+ existingImportStatement = {match: importMatch, statement: newStatement, range: found.range}
+ else
+ lastMatchIndex = importMatch.index
+ )
+
+ if isDefined
+ atom.notifications.addWarning('Import '+importedSymbol+' is already defined.');
+ else
+ insertText = null
+ insertRange = null
+ if hasImportStatements
+ if existingImportStatement
+ insertText = existingImportStatement.statement
+ insertRange = existingImportStatement.range
+ else
+ insertText = @createNewImportStatement(importedSymbol, relativePath, isDefaultImport)
+ insertRange = lastImportRange.copy()
+ insertRange.start = insertRange.end.copy()#insert after the lastImport
+ else
+ insertText = @createNewImportStatement(importedSymbol, relativePath, isDefaultImport) + @getNewLineChar()
+ endPos = @getRangeForAll(editor)
+ insertRange = @getInsertRange(editor, /\/\/\/\s*\s*$/gm, endPos)
+ if !insertRange
+ insertRange = @getInsertRange(editor, /.*[\'\"]use strict[\'\"].*/, endPos)
+ if !insertRange
+ insertRange = new Range([0,0], [0,0])
+
+ editor.setTextInBufferRange(insertRange, insertText)
+
+ getInsertRange: (editor, regexp, searchRange) ->
+ range = null
+ editor.backwardsScanInBufferRange(regexp, searchRange, (found) ->
+ range = found.range.copy()
+ range.start = range.end.copy()#set insert after found match
+ found.stop()
+ )
+ return range
+
+ getRangeForAll: (editor) ->
+ lastRow = editor.getLastBufferRow()
+ return new Range([0,0],[lastRow, editor.lineTextForBufferRow(lastRow).length])
+
+ insert: ->
+ editor = atom.workspace.getActiveTextEditor()
+ @buildIndex()
+ @bindEvent(editor)
+ os = require('os')
+ path = require('path')
+ position = editor.getCursorBufferPosition()
+ editor.selectWordsContainingCursors()
+ selection = editor.getSelectedText().trim()
+ filePath = editor.getPath()
+ symbol = @index[selection]
+
+ if symbol && selection
+ location = symbol.path;
+ defaultImport = symbol.defaultImport;
+ fileFolder = path.resolve(filePath + '/..');
+ relative = path.relative(fileFolder, location).replace(/\.(jsx?|tsx?)$/, '');
+ # Replace the windows path seperator with '/'
+ if (os.platform() == 'win32')
+ relative = relative.split(path.sep).join('/');
+
+ if(!/^\./.test(relative))
+ relative = './' + relative;
+
+ @addImportStatement(selection, relative, defaultImport)
+ else
+ atom.notifications.addError('Symbol '+selection+' not found.');
+
+ createNewImportStatement: (importedSymbol, relativePath, isDefaultImport) ->
+ nl = @getNewLineChar()
+
+ if(isDefaultImport)
+ importStatement = "#{nl}import #{importedSymbol} from '#{relativePath}';"
+ else
+ importStatement = "#{nl}import { #{importedSymbol} } from '#{relativePath}';"
+
+
+ getNewLineChar: ->
+ switch atom.config.get('line-ending-selector.defaultLineEnding')
+ when 'CRLF' then return '\r\n'
+ when 'LF' then return '\n'
+ # when 'OS Default' then
+ else
+ os = require('os')
+ if os.platform() == 'win32'
+ return '\r\n'
+ else
+ return '\n'
+
+
+ isInComment: (startIndex, previousIndex, text) ->
+ if @isInMultiLineComment(startIndex, previousIndex, text)
+ return true
+ else
+ return @isInLineComment(startIndex, previousIndex, text)
+
+ isInLineComment: (startIndex, previousIndex, text) ->
+ reLineComment = /\/\//g
+ reLineComment.lastIndex = previousIndex
+
+ #find last line-comment before startIndex
+ while (match = reLineComment.exec(text)) && match.index < startIndex
+ lastComment = match
+
+ #check if last line-comment is in same line as startIndex
+ if lastComment
+ reNewLine = /\r|\n/g
+ reNewLine.lastIndex = lastComment.index
+ if match = reNewLine.exec(text)
+ #if match.index > startIndex#DEGUB
+ # console.warn('import is within line-comment')
+ return match.index > startIndex # if the next NL comes after the import statement -> import statement is within the line-comment
+
+ return false
+
+ isInMultiLineComment: (startIndex, previousIndex, text) ->
+ reComment = /\/\*\*?/g
+ reClosing = /\*\//
+ reComment.lastIndex = previousIndex
+ isOpen = false
+ #check if last multi-line comment is open for startIndex
+ while (commentMatch = reComment.exec(text)) && commentMatch.index < startIndex
+ reClosing.lastIndex = commentMatch.index
+ if closingMatch = reClosing.exec() && closingMatch.index < startIndex
+ isOpen = false
+ else
+ isOpen = true
+
+ #if isOpen#DEBUG
+ # console.warn('import in within multi-line-comment')
+
+ return isOpen
+
+ isImportFromFile: (regexImportStatement, symbolPath) ->
+ importPathRaw = regexImportStatement[2]#regarding index-access [2]: see RegExp definition for matchImports in addImportStatement()
+ importPathMatch = /'([^']+)'|"([^"]+)"/gm.exec(importPathRaw);
+ if importPathMatch
+ if importPathMatch[1]
+ importPath = importPathMatch[1]
+ else
+ importPath = importPathMatch[2]
+ if !importPath
+ console.log('could not extract file path from import statement', regexImportStatement)
+ return false
+ start = 0
+ end = importPath.length
+ if /^\.\/\.\./.test(importPath)
+ start = 2
+ #TODO normalize paths properly
+ importPath = importPath.substring(start, end)
+ return (importPath == symbolPath)
+
+ extractSymbolStringFrom: (regexImportStatement) ->
+ importStatement = regexImportStatement[0];
+ if /\{/gm.test(importStatement)
+ strSymbols = /.*\{([^}]+)\}/g.exec(importStatement)
+ else
+ strSymbols = /.*?import((.|\r|\n)+?)from/g.exec(importStatement)
+
+ containsSymbol: (regexImportStatement, newSymbol) ->
+ symbolStrList = @extractSymbolStringFrom(regexImportStatement)
+ reTest = new RegExp('\\s*' + newSymbol + '\\s*(,|$)', 'gm')
+ i = 0
+ size = symbolStrList.length
+ while i < size
+ if reTest.test(symbolStrList[i])
+ return true
+ ++i
+ return false
+
+ insertIntoStatement: (regexImportStatement, newSymbol, isDefaultImport) ->
+ #TODO handle default import?: "import {...} form ..." -> "import newSymbol, {...} form ..."
+ importStatement = regexImportStatement[0];
+ sb = [', ', newSymbol, ' '];
+ if /\{/gm.test(importStatement)
+ insertIndex = importStatement.lastIndexOf('}');
+ else
+ insertIndex = /from(.*)$/gm.exec(importStatement).index;#ASSERT there is at least on match, due to RegExp definition in addImportStatement()
+ sb[0] = '{ '
+ sb.push('}')
+ sb.unshift(importStatement.substring(0, insertIndex))
+ sb.push(importStatement.substring(insertIndex, importStatement.length))
+ return sb.join('')
+
+ buildIndex: ->
+ index = @index;
+ searchPaths = ['**/*.ts', '**/*.js', '**/*.tsx', '**/*.jsx'];
+ symbolPattern = /export\s*default\s*(class|interface|namespace|enum|const|type|function)?\s*(([a-zA-Z0-9])*)/
+ atom.workspace.scan(symbolPattern, { paths: searchPaths }, (result) ->
+ for res in result.matches
+ rawSymbol = res.matchText
+ symbol = rawSymbol.match(symbolPattern)[2];
+ index[symbol] = { path: result.filePath, defaultImport: true };
+ );
+ symbolPatternNoDefault = /export *(class|interface|namespace|enum|const|type|function)?\s*(([a-zA-Z0-9])*)/
+ atom.workspace.scan(symbolPatternNoDefault, { paths: searchPaths }, (result) ->
+ for res in result.matches
+ rawSymbol = res.matchText
+ symbol = rawSymbol.match(symbolPatternNoDefault)[2];
+ index[symbol] = { path: result.filePath, defaultImport: false };
+ );
+ getIndex: ->
+ @index