Repository: hannah-sten/texify-idea
Issue: \\usepackage is inserted in invalid location in included file 

<!-- **Note**: you can discard the template below if you are requesting support or proposing a new feature. -->

#### Type of JetBrains IDE (IntelliJ, PyCharm, etc.) and version
IntelliJ IDEA 2019.1.3

#### TeXiFy IDEA version
v0.6.5

#### What I did (steps to reproduce)
paste a sentence that includes a reference like "\\citet{authorYear}" into a file. 
this file is part of a tex-document consisting of several files.

#### Expected behavior
just paste that sentence.
please do not import packages automatically!

#### Actual behavior
at the beginning of this particular document, texify added \\usepackage{natbib}.
as my document consists of several files, this file is only included in a document.
Hence, the usepackage{} has not been placed in the preamble!
If I remember correctly, this auto import also happened before with other packages

#### (if applicable) The full stacktrace of the exception thrown
```

```

Changed files:
src/nl/hannahsten/texifyidea/completion/handlers/LatexNoMathInsertHandler.kt
src/nl/hannahsten/texifyidea/util/Packages.kt
src/nl/hannahsten/texifyidea/util/files/Files.kt
src/nl/hannahsten/texifyidea/inspections/latex/LatexUnicodeInspection.kt
src/nl/hannahsten/texifyidea/completion/handlers/LatexCommandPackageIncludeHandler.kt
src/nl/hannahsten/texifyidea/action/insert/InsertStrikethroughAction.kt
src/nl/hannahsten/texifyidea/editor/LatexQuoteInsertHandler.kt

diff --git a/src/nl/hannahsten/texifyidea/action/insert/InsertStrikethroughAction.kt b/src/nl/hannahsten/texifyidea/action/insert/InsertStrikethroughAction.kt
index a1e0fff15..bf288e686 100644
--- a/src/nl/hannahsten/texifyidea/action/insert/InsertStrikethroughAction.kt
+++ b/src/nl/hannahsten/texifyidea/action/insert/InsertStrikethroughAction.kt
@@ -6,7 +6,8 @@ import com.intellij.openapi.vfs.VirtualFile
 import com.intellij.psi.PsiManager
 import nl.hannahsten.texifyidea.TexifyIcons
 import nl.hannahsten.texifyidea.action.InsertEditorAction
-import nl.hannahsten.texifyidea.util.PackageUtils
+import nl.hannahsten.texifyidea.lang.Package.Companion.ULEM
+import nl.hannahsten.texifyidea.util.insertUsepackage
 
 /**
  * @author Hannah Schellekens
@@ -20,12 +21,10 @@ class InsertStrikethroughAction : InsertEditorAction(
     override fun actionPerformed(file: VirtualFile, project: Project, textEditor: TextEditor) {
         super.actionPerformed(file, project, textEditor)
 
-        val document = textEditor.editor.document
         val psiFile = PsiManager.getInstance(project).findFile(file) ?: return
-        val packageName = "ulem"
 
         runWriteAction(project) {
-            PackageUtils.insertUsepackage(document, psiFile, packageName, null)
+            psiFile.insertUsepackage(ULEM)
         }
     }
 }
diff --git a/src/nl/hannahsten/texifyidea/completion/handlers/LatexCommandPackageIncludeHandler.kt b/src/nl/hannahsten/texifyidea/completion/handlers/LatexCommandPackageIncludeHandler.kt
index f56bdbd1d..f2e81ba2d 100644
--- a/src/nl/hannahsten/texifyidea/completion/handlers/LatexCommandPackageIncludeHandler.kt
+++ b/src/nl/hannahsten/texifyidea/completion/handlers/LatexCommandPackageIncludeHandler.kt
@@ -3,12 +3,8 @@ package nl.hannahsten.texifyidea.completion.handlers
 import com.intellij.codeInsight.completion.InsertHandler
 import com.intellij.codeInsight.completion.InsertionContext
 import com.intellij.codeInsight.lookup.LookupElement
-import com.intellij.openapi.editor.Document
-import com.intellij.openapi.util.text.StringUtil
-import com.intellij.psi.PsiFile
 import nl.hannahsten.texifyidea.lang.LatexCommand
-import nl.hannahsten.texifyidea.lang.Package.Companion.DEFAULT
-import nl.hannahsten.texifyidea.util.PackageUtils
+import nl.hannahsten.texifyidea.util.insertUsepackage
 
 /**
  * @author Hannah Schellekens
@@ -17,23 +13,6 @@ class LatexCommandPackageIncludeHandler : InsertHandler<LookupElement> {
 
     override fun handleInsert(insertionContext: InsertionContext, item: LookupElement) {
         val command = item.`object` as LatexCommand
-        handleInsert(insertionContext.document, insertionContext.file, command)
-    }
-
-    fun handleInsert(document: Document, file: PsiFile, command: LatexCommand) {
-        val pack = command.dependency
-        if (pack == DEFAULT) {
-            return
-        }
-
-        val includedPackages = PackageUtils.getIncludedPackages(file)
-        if (!includedPackages.contains(pack.name)) {
-            PackageUtils.insertUsepackage(
-                    document,
-                    file,
-                    pack.name,
-                    StringUtil.join(pack.parameters, ",")
-            )
-        }
+        insertionContext.file.insertUsepackage(command.dependency)
     }
 }
diff --git a/src/nl/hannahsten/texifyidea/completion/handlers/LatexNoMathInsertHandler.kt b/src/nl/hannahsten/texifyidea/completion/handlers/LatexNoMathInsertHandler.kt
index b723563f3..4372827bb 100644
--- a/src/nl/hannahsten/texifyidea/completion/handlers/LatexNoMathInsertHandler.kt
+++ b/src/nl/hannahsten/texifyidea/completion/handlers/LatexNoMathInsertHandler.kt
@@ -63,7 +63,7 @@ class LatexNoMathInsertHandler : InsertHandler<LookupElement> {
                     .toSet()
 
             // Include packages.
-            if (!PackageUtils.getIncludedPackages(file).contains(pack.name) && envName !in envDefinitions) {
+            if (!file.includedPackages().contains(pack.name) && envName !in envDefinitions) {
                 file.insertUsepackage(pack)
             }
 
diff --git a/src/nl/hannahsten/texifyidea/editor/LatexQuoteInsertHandler.kt b/src/nl/hannahsten/texifyidea/editor/LatexQuoteInsertHandler.kt
index cab9a210e..55ad7836e 100644
--- a/src/nl/hannahsten/texifyidea/editor/LatexQuoteInsertHandler.kt
+++ b/src/nl/hannahsten/texifyidea/editor/LatexQuoteInsertHandler.kt
@@ -7,10 +7,10 @@ import com.intellij.openapi.editor.Editor
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.util.TextRange
 import com.intellij.psi.PsiFile
-import nl.hannahsten.texifyidea.completion.handlers.LatexCommandPackageIncludeHandler
 import nl.hannahsten.texifyidea.file.LatexFileType
 import nl.hannahsten.texifyidea.lang.LatexRegularCommand
 import nl.hannahsten.texifyidea.settings.TexifySettings
+import nl.hannahsten.texifyidea.util.insertUsepackage
 
 /**
  * This class performs smart quote substitution. When this is enabled, it will replace double quotes " and single quotes ' with the appropriate LaTeX symbols.
@@ -106,10 +106,10 @@ open class LatexQuoteInsertHandler : TypedHandlerDelegate() {
 
             // Package dependencies
             if (char == '"') {
-                LatexCommandPackageIncludeHandler().handleInsert(document, file, LatexRegularCommand.ENQUOTE)
+                file.insertUsepackage(LatexRegularCommand.ENQUOTE.dependency)
             }
             else {
-                LatexCommandPackageIncludeHandler().handleInsert(document, file, LatexRegularCommand.ENQUOTE_STAR)
+                file.insertUsepackage(LatexRegularCommand.ENQUOTE_STAR.dependency)
             }
         }
     }
diff --git a/src/nl/hannahsten/texifyidea/inspections/latex/LatexUnicodeInspection.kt b/src/nl/hannahsten/texifyidea/inspections/latex/LatexUnicodeInspection.kt
index f503a3218..284e61618 100644
--- a/src/nl/hannahsten/texifyidea/inspections/latex/LatexUnicodeInspection.kt
+++ b/src/nl/hannahsten/texifyidea/inspections/latex/LatexUnicodeInspection.kt
@@ -27,6 +27,7 @@ import nl.hannahsten.texifyidea.settings.TexifyProjectConfigurable
 import nl.hannahsten.texifyidea.settings.TexifyProjectSettings
 import nl.hannahsten.texifyidea.util.Magic
 import nl.hannahsten.texifyidea.util.PackageUtils
+import nl.hannahsten.texifyidea.util.insertUsepackage
 import org.jetbrains.annotations.Nls
 import java.text.Normalizer
 import java.util.regex.Pattern
@@ -139,24 +140,10 @@ class LatexUnicodeInspection : TexifyInspectionBase() {
         }
 
         override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
-            val element = descriptor.psiElement
-            val file = element.containingFile
-            val document = PsiDocumentManager.getInstance(project).getDocument(file)
-            val included = PackageUtils.getIncludedPackages(file)
-
-            if (document == null) {
-                return
-            }
+            val file = descriptor.psiElement.containingFile
 
             Magic.Package.unicode.forEach { p ->
-                if (included.contains(p.name)) {
-                    return
-                }
-
-                PackageUtils.insertUsepackage(
-                        document, file, p.name,
-                        p.parameters.joinToString(",")
-                )
+                file.insertUsepackage(p)
             }
         }
     }
diff --git a/src/nl/hannahsten/texifyidea/util/Packages.kt b/src/nl/hannahsten/texifyidea/util/Packages.kt
index f9f0c844e..f2c4476b0 100644
--- a/src/nl/hannahsten/texifyidea/util/Packages.kt
+++ b/src/nl/hannahsten/texifyidea/util/Packages.kt
@@ -7,6 +7,7 @@ import nl.hannahsten.texifyidea.index.LatexCommandsIndex
 import nl.hannahsten.texifyidea.lang.Package
 import nl.hannahsten.texifyidea.psi.LatexCommands
 import nl.hannahsten.texifyidea.util.files.document
+import nl.hannahsten.texifyidea.util.files.findRootFile
 import nl.hannahsten.texifyidea.util.files.isClassFile
 import nl.hannahsten.texifyidea.util.files.isStyleFile
 
@@ -33,6 +34,7 @@ object PackageUtils {
 
     /**
      * Inserts a usepackage statement for the given package in a certain file.
+     * todo all callers should use other usepackage?
      *
      * @param file
      *          The file to add the usepackage statement to.
@@ -42,7 +44,7 @@ object PackageUtils {
      *          Parameters to add to the statement, `null` or empty string for no parameters.
      */
     @JvmStatic
-    fun insertUsepackage(document: Document, file: PsiFile, packageName: String, parameters: String?) {
+    private fun insertUsepackage(document: Document, file: PsiFile, packageName: String, parameters: String?) {
         val commands = LatexCommandsIndex.getItems(file)
 
         val commandName = if (file.isStyleFile() || file.isClassFile()) "\\\\RequirePackage" else "\\\\usepackage"
@@ -93,7 +95,7 @@ object PackageUtils {
     }
 
     /**
-     * Inserts a usepackage statement for the given package in a certain file.
+     * Inserts a usepackage statement for the given package in the root file of the fileset containing the given file.
      * Will not insert a new statement when the package has already been included.
      *
      * @param file
@@ -111,11 +113,14 @@ object PackageUtils {
             return
         }
 
-        val document = file.document() ?: return
+        // Packages should always be included in the root file
+        val rootFile = file.findRootFile()
+
+        val document = rootFile.document() ?: return
 
         val params = pack.parameters
         val parameterString = StringUtil.join(params, ",")
-        insertUsepackage(document, file, pack.name, parameterString)
+        insertUsepackage(document, rootFile, pack.name, parameterString)
     }
 
     /**
@@ -250,14 +255,6 @@ object PackageUtils {
  */
 fun PsiFile.insertUsepackage(pack: Package) = PackageUtils.insertUsepackage(this, pack)
 
-/**
- * @see PackageUtils.insertUsepackage
- */
-fun PsiFile.insertUsepackage(packageName: String, parameters: String?) {
-    val document = document() ?: return
-    PackageUtils.insertUsepackage(document, this, packageName, parameters)
-}
-
 /**
  * @see PackageUtils.getIncludedPackages
  */
diff --git a/src/nl/hannahsten/texifyidea/util/files/Files.kt b/src/nl/hannahsten/texifyidea/util/files/Files.kt
index 6085b8ca5..1095532be 100644
--- a/src/nl/hannahsten/texifyidea/util/files/Files.kt
+++ b/src/nl/hannahsten/texifyidea/util/files/Files.kt
@@ -164,7 +164,8 @@ fun PsiFile.findRootFile(): PsiFile {
             return@forEach
         }
 
-        if (file.isIncludedBy(file, inclusions)) {
+        // If the root file contains this, we have found the root file
+        if (file.contains(this, inclusions)) {
             return file
         }
     }
@@ -181,7 +182,7 @@ fun PsiFile.findRootFile(): PsiFile {
  *              Map that maps each psi file to all the files that get included by said file.
  * @return `true` when `childMaybe` is a child of `this` file, `false` otherwise.
  */
-private fun PsiFile.isIncludedBy(childMaybe: PsiFile, mapping: Map<PsiFile, Set<PsiFile>>): Boolean {
+private fun PsiFile.contains(childMaybe: PsiFile, mapping: Map<PsiFile, Set<PsiFile>>): Boolean {
     return IsChildDFS(
             this,
             { mapping[it] ?: emptySet() },
@@ -207,6 +208,11 @@ fun Project.allFileinclusions(): Map<PsiFile, Set<PsiFile>> {
         val declaredIn = command.containingFile
         val referenced = declaredIn.findRelativeFile(includedName, null) ?: continue
 
+        // When it looks like a file includes itself, we skip it
+        if (declaredIn.viewProvider.virtualFile.nameWithoutExtension == includedName) {
+            continue
+        }
+
         val inclusionSet = inclusions[declaredIn] ?: HashSet()
         inclusionSet.add(referenced)
         inclusions[declaredIn] = inclusionSet
@@ -310,9 +316,10 @@ private fun PsiFile.referencedFiles(files: MutableCollection<PsiFile>) {
     val scope = fileSearchScope
     val commands = LatexCommandsIndex.getItems(project, scope)
 
+    val rootFile = findRootFile()
+
     commands.forEach { command ->
         val fileName = command.includedFileName() ?: return@forEach
-        val rootFile = findRootFile()
         val extensions = Magic.Command.includeOnlyExtensions[command.commandToken.text]
         val included = rootFile.findRelativeFile(fileName, extensions) ?: return@forEach
         if (included in files) return@forEach